diff --git a/channel.cpp b/channel.cpp index dc903a4..30099b5 100644 --- a/channel.cpp +++ b/channel.cpp @@ -282,7 +282,7 @@ struct t_channel *channel__new(struct t_account *account, enum t_channel_type type, const char *id, const char *name) { - struct t_channel *new_channel, *ptr_channel; + struct t_channel *new_channel, *ptr_channel, *muc_channel; struct t_gui_buffer *ptr_buffer; struct t_hook *typing_timer, *self_typing_timer; @@ -300,10 +300,10 @@ struct t_channel *channel__new(struct t_account *account, return NULL; else if (type == CHANNEL_TYPE_PM) { - ptr_channel = channel__search(account, jid(account->context, id).bare.data()); - if (ptr_channel) + muc_channel = channel__search(account, jid(account->context, id).bare.data()); + if (muc_channel) { - weechat_buffer_merge(ptr_buffer, ptr_channel->buffer); + weechat_buffer_merge(ptr_buffer, muc_channel->buffer); } } @@ -893,11 +893,7 @@ void channel__update_topic(struct t_channel *channel, void channel__update_name(struct t_channel *channel, const char* name) { - if (channel->name) - free(channel->name); - channel->name = (name) ? strdup(name) : NULL; - - if (channel->name) + if (name) weechat_buffer_set(channel->buffer, "short_name", name); else weechat_buffer_set(channel->buffer, "short_name", ""); @@ -1149,6 +1145,23 @@ int channel__send_message(struct t_account *account, struct t_channel *channel, { std::string url { &*match[0].first, static_cast(match[0].length()) }; + //struct t_hashtable *options = weechat_hashtable_new (8, + // WEECHAT_HASHTABLE_STRING, + // WEECHAT_HASHTABLE_STRING, + // NULL, + // NULL); + //if (!options) { return; } + //weechat_hashtable_set(options, "nobody", "1"); + //auto command = "url:" + url; + //const int timeout = 30000; + //struct t_hook *process_hook = + // weechat_hook_process_hashtable(command, options, timeout, + // int (*callback)(const void *pointer, void *data, + // const char *command, + // int return_code, const char *out, const char *err), + // const void *callback_pointer, void *callback_data); + //weechat_hashtable_free(options); + xmpp_stanza_t *message__x = xmpp_stanza_new(account->context); xmpp_stanza_set_name(message__x, "x"); xmpp_stanza_set_ns(message__x, "jabber:x:oob"); diff --git a/command.cpp b/command.cpp index 5b613ad..e8e769d 100644 --- a/command.cpp +++ b/command.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include "plugin.hh" @@ -17,6 +19,7 @@ #include "buffer.hh" #include "message.hh" #include "command.hh" +#include "sexp/driver.hh" #define MAM_DEFAULT_DAYS 2 #define STR(X) #X @@ -902,17 +905,41 @@ int command__xml(const void *pointer, void *data, if (argc > 1) { - stanza = xmpp_stanza_new_from_string(ptr_account->context, - argv_eol[1]); - if (!stanza) + auto parse = [&](sexp::driver& sxml) { + std::stringstream ss; + std::string line; + try { + return sxml.parse(argv_eol[1], &ss); + } + catch (const std::invalid_argument& ex) { + while (std::getline(ss, line)) + weechat_printf(nullptr, "%ssxml: %s", weechat_prefix("info"), line.data()); + weechat_printf(nullptr, "%ssxml: %s", weechat_prefix("error"), ex.what()); + return false; + } + }; + if (sexp::driver sxml(ptr_account->context); parse(sxml)) { - weechat_printf(nullptr, _("%s%s: Bad XML"), - weechat_prefix("error"), WEECHAT_XMPP_PLUGIN_NAME); - return WEECHAT_RC_ERROR; + for (auto *stanza : sxml.elements) + { + xmpp_send(ptr_account->connection, stanza); + xmpp_stanza_release(stanza); + } } + else + { + stanza = xmpp_stanza_new_from_string(ptr_account->context, + argv_eol[1]); + if (!stanza) + { + weechat_printf(nullptr, _("%s%s: Bad XML"), + weechat_prefix("error"), WEECHAT_XMPP_PLUGIN_NAME); + return WEECHAT_RC_ERROR; + } - xmpp_send(ptr_account->connection, stanza); - xmpp_stanza_release(stanza); + xmpp_send(ptr_account->connection, stanza); + xmpp_stanza_release(stanza); + } } return WEECHAT_RC_OK; diff --git a/connection.cpp b/connection.cpp index 8cc7ac3..5bf4c43 100644 --- a/connection.cpp +++ b/connection.cpp @@ -974,36 +974,40 @@ int connection__iq_handler(xmpp_conn_t *conn, xmpp_stanza_t *stanza, void *userd if (weechat_strcasecmp(type, "result") == 0) { xmpp_stanza_t *identity = xmpp_stanza_get_child_by_name(query, "identity"); - std::string category; - std::string name; - std::string type; - - if (const char *attr = xmpp_stanza_get_attribute(identity, "category")) - category = attr; - if (const char *attr = xmpp_stanza_get_attribute(identity, "name")) - name = unescape(attr); - if (const char *attr = xmpp_stanza_get_attribute(identity, "type")) - type = attr; - - if (category == "conference") - { - struct t_channel *ptr_channel = channel__search(account, from); - if (ptr_channel) - channel__update_name(ptr_channel, name.data()); - } - else if (category == "conference") + if (identity) { - xmpp_stanza_t *children[2] = {NULL}; - children[0] = stanza__iq_pubsub_items(account->context, NULL, - const_cast("eu.siacs.conversations.axolotl.devicelist")); - children[0] = stanza__iq_pubsub(account->context, NULL, - children, with_noop("http://jabber.org/protocol/pubsub")); - children[0] = stanza__iq(account->context, NULL, children, NULL, - strdup("fetch2"), to ? strdup(to) : NULL, - binding.from ? strdup(binding.from->bare.data()) : NULL, strdup("get")); - xmpp_send(conn, children[0]); - xmpp_stanza_release(children[0]); + std::string category; + std::string name; + std::string type; + + if (const char *attr = xmpp_stanza_get_attribute(identity, "category")) + category = attr; + if (const char *attr = xmpp_stanza_get_attribute(identity, "name")) + name = unescape(attr); + if (const char *attr = xmpp_stanza_get_attribute(identity, "type")) + type = attr; + + if (category == "conference") + { + struct t_channel *ptr_channel = channel__search(account, from); + + if (ptr_channel) + channel__update_name(ptr_channel, name.data()); + } + else if (category == "conference") + { + xmpp_stanza_t *children[2] = {NULL}; + children[0] = stanza__iq_pubsub_items(account->context, NULL, + const_cast("eu.siacs.conversations.axolotl.devicelist")); + children[0] = stanza__iq_pubsub(account->context, NULL, + children, with_noop("http://jabber.org/protocol/pubsub")); + children[0] = stanza__iq(account->context, NULL, children, NULL, + strdup("fetch2"), to ? strdup(to) : NULL, + binding.from ? strdup(binding.from->bare.data()) : NULL, strdup("get")); + xmpp_send(conn, children[0]); + xmpp_stanza_release(children[0]); + } } } } diff --git a/makefile b/makefile index 0cab009..13b5466 100644 --- a/makefile +++ b/makefile @@ -39,28 +39,28 @@ LDLIBS=-lstrophe \ $(shell pkg-config --libs gpgme) \ $(shell pkg-config --libs libsignal-protocol-c) \ -lgcrypt \ - -llmdb + -llmdb -lfl PREFIX ?= /usr/local LIBDIR ?= $(PREFIX)/lib HDRS=plugin.hh \ - account.hh \ - buffer.hh \ - channel.hh \ - command.hh \ - completion.hh \ - config.hh \ - connection.hh \ - input.hh \ - message.hh \ - omemo.hh \ - pgp.hh \ - user.hh \ - util.hh \ - xmpp/stanza.hh \ - xmpp/ns.hh \ - xmpp/node.hh \ + account.hh \ + buffer.hh \ + channel.hh \ + command.hh \ + completion.hh \ + config.hh \ + connection.hh \ + input.hh \ + message.hh \ + omemo.hh \ + pgp.hh \ + user.hh \ + util.hh \ + xmpp/stanza.hh \ + xmpp/ns.hh \ + xmpp/node.hh \ SRCS=plugin.cpp \ account.cpp \ @@ -82,16 +82,19 @@ SRCS=plugin.cpp \ DEPS=deps/diff/libdiff.a \ deps/fmt/libfmt.a \ + sexp/sexp.a \ OBJS=$(patsubst %.cpp,.%.o,$(patsubst %.c,.%.o,$(patsubst xmpp/%.cpp,xmpp/.%.o,$(SRCS)))) COVS=$(patsubst %.cpp,.%.cov.o,$(patsubst xmpp/%.cpp,xmpp/.%.cov.o,$(SRCS))) SUFFIX=$(shell date +%s) +.PHONY: all all: make depend make weechat-xmpp && make test +.PHONY: weechat-xmpp release weechat-xmpp: $(DEPS) xmpp.so release: xmpp.so cp xmpp.so .xmpp.so.$(SUFFIX) @@ -102,6 +105,20 @@ xmpp.so: $(OBJS) $(DEPS) $(HDRS) git ls-files | xargs ls -d | xargs tar cz | objcopy --add-section .source=/dev/stdin xmpp.so #objcopy --dump-section .source=/dev/stdout xmpp.so | tar tz +sexp/sexp.a: sexp/driver.o sexp/parser.o sexp/lexer.o + ar -r $@ $^ + +sexp/parser.o: sexp/parser.yy + cd sexp && bison -t -d -v parser.yy + $(CXX) $(CPPFLAGS) -c sexp/parser.tab.cc -o $@ + +sexp/lexer.o: sexp/lexer.l + cd sexp && flex -d --outfile=lexer.yy.cc lexer.l + $(CXX) $(CPPFLAGS) -c sexp/lexer.yy.cc -o $@ + +sexp/driver.o: sexp/driver.cpp + $(CXX) $(CPPFLAGS) -c $< -o $@ + .%.o: %.c $(eval GIT_REF=$(shell git describe --abbrev=6 --always --dirty 2>/dev/null || true)) $(CC) -DGIT_COMMIT=$(GIT_REF) $(CFLAGS) -c $< -o $@ @@ -119,6 +136,7 @@ xmpp/.%.o: xmpp/%.cpp xmpp/.%.cov.o: xmpp/%.cpp @$(CXX) --coverage $(CPPFLAGS) -O0 -c $< -o $@ +.PHONY: diff fmt deps/diff/libdiff.a: git submodule update --init --recursive cd deps/diff && env -u MAKEFLAGS ./configure @@ -137,16 +155,20 @@ tests/xmpp.cov.so: $(COVS) $(DEPS) $(HDRS) tests/run: $(COVS) tests/main.cc tests/xmpp.cov.so env --chdir tests $(CXX) $(CPPFLAGS) -o run ./xmpp.cov.so main.cc $(LDLIBS) +.PHONY: test test: tests/run env --chdir tests ./run -s +.PHONY: coverage coverage: tests/run gcov -m -abcfu -rqk -i .*.gcda xmpp/.*.gcda +.PHONY: debug debug: xmpp.so env LD_PRELOAD=$(DEBUG) gdb -ex "handle SIGPIPE nostop noprint pass" --args \ weechat -a -P 'alias,buflist,exec,irc,relay' -r '/plugin load ./xmpp.so' +.PHONY: depend depend: $(SRCS) $(HDRS) $(RM) -f ./.depend echo > ./.depend @@ -161,21 +183,29 @@ depend: $(SRCS) $(HDRS) done sed -i 's/\.\([a-z]*\/\)/\1./' .depend +.PHONY: tidy tidy: $(FIND) . -name "*.o" -delete $(FIND) . -name "*.gcno" -delete $(FIND) . -name "*.gcda" -delete +.PHONY: clean clean: - $(RM) -f $(OBJS) $(COVS) + $(RM) -f $(OBJS) $(COVS) \ + sexp/parser.tab.cc sexp/parser.tab.hh \ + sexp/location.hh sexp/position.hh \ + sexp/stack.hh sexp/parser.output sexp/parser.o \ + sexp/lexer.o sexp/lexer.yy.cc sexp/sexp.a $(MAKE) -C deps/diff clean || true $(MAKE) -C deps/fmt clean || true git submodule foreach --recursive git clean -xfd || true git submodule foreach --recursive git reset --hard || true +.PHONY: distclean distclean: clean $(RM) *~ .depend +.PHONY: install install: xmpp.so ifeq ($(shell id -u),0) mkdir -p $(DESTDIR)$(LIBDIR)/weechat/plugins @@ -187,8 +217,7 @@ else chmod 755 ~/.weechat/plugins/xmpp.so endif -.PHONY: all weechat-xmpp release test debug depend tidy clean distclean install check - +.PHONY: check check: clang-check --analyze *.c *.cc *.cpp diff --git a/sexp/driver.cpp b/sexp/driver.cpp new file mode 100644 index 0000000..5d586b5 --- /dev/null +++ b/sexp/driver.cpp @@ -0,0 +1,93 @@ +#include +#include +#include +#include + +#include "driver.hh" + +sexp::driver::~driver() +{ + delete(scanner); + scanner = nullptr; + delete(parser); + parser = nullptr; +} + +bool sexp::driver::parse(const char *text, std::ostream *debug = nullptr) +{ + assert(text != nullptr); + std::istringstream stream{std::string(text)}; + return parse(stream, debug); +} + +bool sexp::driver::parse(std::istream &stream, std::ostream *debug = nullptr) +{ + if(!stream.good() && stream.eof()) + { + return false; + } + return parse_helper(stream, debug); +} + + +bool sexp::driver::parse_helper(std::istream &stream, std::ostream *debug) +{ + delete(scanner); + try + { + scanner = new sexp::scanner(&stream); + } + catch(std::bad_alloc &ba) + { + throw std::runtime_error("Failed to allocate scanner"); + } + + delete(parser); + try + { + parser = new sexp::parser((*scanner) /* scanner */, + (*this) /* driver */); + } + catch(std::bad_alloc &ba) + { + throw std::runtime_error("Failed to allocate parser"); + } + + const int accept = 0; + if (debug) + { + parser->set_debug_level(1); + parser->set_debug_stream(*debug); + } + return parser->parse() == accept; +} + +void sexp::driver::start_tag(const std::string &name) +{ + auto *stanza = xmpp_stanza_new(context); + xmpp_stanza_set_name(stanza, name.data()); + stack.push_back(stanza); +} + +void sexp::driver::end_tag() +{ + auto *stanza = stack.back(); + stack.pop_back(); + if (stack.empty()) + elements.push_back(stanza); + else + xmpp_stanza_add_child_ex(stack.back(), stanza, false); +} + +void sexp::driver::add_text(const std::string &text) +{ + auto *stanza = xmpp_stanza_new(context); + xmpp_stanza_set_text(stanza, text.substr(1,text.length()-2).data()); + xmpp_stanza_add_child_ex(stack.back(), stanza, false); +} + +void sexp::driver::add_attr(const std::string &name, const std::string &value) +{ + xmpp_stanza_set_attribute(stack.back(), name.data(), + value.substr(1,value.length()-2).data()); +} diff --git a/sexp/driver.hh b/sexp/driver.hh new file mode 100644 index 0000000..1e757ca --- /dev/null +++ b/sexp/driver.hh @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "scanner.hh" +#include "parser.tab.hh" + +namespace sexp { + + class driver { + public: + driver(xmpp_ctx_t *context) : context(context) {} + + virtual ~driver(); + + /** + * parse - parse from a file + * @param text - valid string + */ + bool parse(const char *text, std::ostream *debug); + + /** + * parse - parse from a c++ input stream + * @param is - std::istream&, valid input stream + */ + bool parse(std::istream &iss, std::ostream *debug); + + void start_tag(const std::string &name); + void end_tag(); + void add_text(const std::string &text); + void add_attr(const std::string &name, const std::string &value); + + std::vector elements; + + private: + bool parse_helper(std::istream &stream, std::ostream *debug); + + xmpp_ctx_t *context; + + std::vector stack; + sexp::parser *parser = nullptr; + sexp::scanner *scanner = nullptr; + }; + +} diff --git a/sexp/lexer.l b/sexp/lexer.l new file mode 100644 index 0000000..69f59c3 --- /dev/null +++ b/sexp/lexer.l @@ -0,0 +1,56 @@ +%option debug +%option nodefault +%option yyclass="sexp::scanner" +%option noyywrap +%option c++ + +%{ + #include + + #include "scanner.hh" + #undef YY_DECL + #define YY_DECL int sexp::scanner::yylex(sexp::parser::semantic_type *const lval, sexp::parser::location_type *loc) + + using token = sexp::parser::token; + + // defaults to NULL + #define yyterminate() return token::END + + // update location on matching + #define YY_USER_ACTION loc->step(); loc->columns(yyleng); +%} + +/* Regular Expressions */ +%% +%{ + yylval = lval; +%} + +"(" { + return token::LPAREN; + } + +")" { + return token::RPAREN; + } + +[\n \t\r]+ { + // Update line number + loc->lines(); + return token::SPACE; + } + +\"([^"\\]|\\.)*\" { + yylval->build(yytext); + return token::STRING; + } + +[^ "(:@)]+ { + yylval->build(yytext); + return token::NAME; + } + +. { + return *yytext; + } +%% diff --git a/sexp/parser.yy b/sexp/parser.yy new file mode 100644 index 0000000..f1ba37b --- /dev/null +++ b/sexp/parser.yy @@ -0,0 +1,78 @@ +%skeleton "lalr1.cc" +%require "3.0" +%debug +%defines + +%define api.namespace {sexp} +%define api.parser.class {parser} + +%code requires{ + namespace sexp { + class driver; + class scanner; + } +} + +%parse-param { sexp::scanner &scanner } +%parse-param { sexp::driver &driver } + +%code{ + #include + #include + #include + + /* include for all driver functions */ + #include "driver.hh" + + #undef yylex + #define yylex scanner.yylex +} + +%define api.value.type variant +%define parse.assert +%define parse.error verbose + +%token END 0 "end of file" +%token LPAREN +%token RPAREN +%token NAME +%token STRING +%token SPACE + +%locations + +%start input + +%% +input : ws END | element input; + +element : lparen attributeset rparenq + | lparen tag children rparen; + +ws : | gap; +gap : SPACE; + +lparen : ws LPAREN; +rparen : ws RPAREN { driver.end_tag(); }; +rparenq : ws RPAREN; + +tag : ws NAME { driver.start_tag($2); } + | ws NAME ':' NAME { driver.start_tag($4); }; + +attributeset : '@' attributes; + +attributes : ws | gap attribute attributes; + +attribute : lparen ws NAME gap STRING rparenq { driver.add_attr($3, $5); }; + +children : ws + | gap STRING children { driver.add_text($2); } + | gap element children; +%% + +void sexp::parser::error(const sexp::parser::location_type &l, const std::string &err_message) +{ + std::ostringstream ss; + ss << "parsing " << err_message << " at " << l; + throw std::invalid_argument(ss.str()); +} diff --git a/sexp/scanner.hh b/sexp/scanner.hh new file mode 100644 index 0000000..20f7639 --- /dev/null +++ b/sexp/scanner.hh @@ -0,0 +1,37 @@ +#pragma once + +#ifndef yyFlexLexer +#include +#endif + +#include "parser.tab.hh" +#include "location.hh" + +namespace sexp { + + class scanner : public yyFlexLexer { + public: + + scanner(std::istream *in) : yyFlexLexer(in) + { + }; + + virtual ~scanner() { + }; + + // get rid of override virtual function warning + using FlexLexer::yylex; + + virtual + int yylex(sexp::parser::semantic_type *const lval, + sexp::parser::location_type *location); + // YY_DECL defined in lexer.l + // Method body created by flex in lexer.yy.cc + + + private: + /* yyval ptr */ + sexp::parser::semantic_type *yylval = nullptr; + }; + +}