Compare commits

...

109 Commits

Author SHA1 Message Date
ddb3a60dcc Experimental support for building as C++
So far with the following caveats:
 - Triggers -Wc99-designator
 - Compound literals are non-standard.
 - The setjmp/longjmp in the configuration parser might be an issue.
 - Perhaps others.

It does not seem to be a good idea to use this library for C++ at all.
Much of what it does is directly replaced by the STL.
2020-10-22 02:19:59 +02:00
53bcebc2f0 Split out utf8_validate_cp(), adhere to RFC 3629 2020-10-21 05:20:20 +02:00
b08cf6c29f Reject overlong UTF-8 sequences 2020-10-21 05:08:59 +02:00
69101eb155 Fix optional arguments in --help output
An equals sign is necessary.
2020-10-13 21:27:46 +02:00
9d14562f7e Improve the UTF-8 API
We need to be able to detect partial sequences.
2020-10-12 22:56:22 +02:00
9b72304963 Fix a memory leak in mpd_client_parse_line() 2020-10-12 02:07:15 +02:00
1cd9ba8d97 Import configuration test from degesch 2020-10-12 02:07:15 +02:00
7e5b6c5343 Fix crashes in the config parser
It had a duality between not requiring null-terminated input
and relying on it, depending on where you looked.
2020-10-12 02:07:14 +02:00
c2c5031538 Add remaining fuzzing entry points
Closes #1
2020-10-12 02:07:07 +02:00
df3f53bd5c Add a basic fuzzing framework using libFuzzer
Updates #1
2020-10-11 20:04:34 +02:00
e029aae1d3 Import xwrite(), cstr_set(), resolve_..._template()
From degesch and json-rpc-shell.
2020-10-10 04:31:52 +02:00
b9457c321f Rename cstr_transform() argument
It does not always have to be tolower().
2020-10-10 04:30:19 +02:00
2201becca4 Mark some issues 2020-10-10 04:29:41 +02:00
7023c51347 Get rid of CMake dev warnings 2020-10-02 06:47:34 +02:00
d21f8466b5 Bump copyright years 2020-10-02 06:43:16 +02:00
7f919025ee Add iscntrl_ascii()
It's too easy to miss the DEL character.
2020-10-02 06:31:46 +02:00
1a76b2032e Add a slogan of sorts 2020-08-01 14:03:23 +02:00
722ef65c1f Name change 2020-08-01 14:02:25 +02:00
317dfcb6e2 Improve setjmp safety in config parser 2020-04-19 07:02:13 +02:00
bca7167d03 Fix the SCGI parser and tests 2018-10-18 06:34:16 +02:00
3e4e4e5103 Allow aborting the FastCGI protocol parser 2018-10-18 04:08:47 +02:00
9494e8e2af Add some comments 2018-10-11 21:02:45 +02:00
8ffe20c0e8 Add missing include for "struct iovec" 2018-06-24 06:09:40 +02:00
bb30c7d86e Remove .travis.yml
We don't depend on any proprietary services no longer.  I'll have to
make my own replacements with blackjack and hookers.  Until then,
the file stays in the commit log as an example.
2018-06-21 23:58:24 +02:00
47ef2ae5bd Update README 2018-06-21 23:58:03 +02:00
69800a6afb Relicense to 0BSD, update mail address
I've come to the conclusion that copyright mostly just stands in the way
of software development.  In my jurisdiction I cannot give up my own
copyright and 0BSD seems to be the closest thing to public domain.

The updated mail address, also used in my author/committer lines,
is shorter and looks nicer.  People rarely interact anyway.
2018-06-21 23:57:25 +02:00
fe1035633a Describe syntax of advanced configuration w/ PEG 2018-04-19 00:09:46 +02:00
da75b6f735 siphash: silence fall-through warnings 2017-09-26 19:08:13 +02:00
199c56e141 Little improvements 2017-07-24 03:46:06 +02:00
6e9217e5d0 MPD client: +mpd_client_send_command_raw() 2017-06-26 03:35:05 +02:00
3835b6e499 Improve simple_config_update_from_file()
- considerably shorter
 - catch file read errors as we should
 - better error messages, now including the filename
 - disallow empty keys as they are never used
 - allow whitespace before start of comment

NUL characters stop processing now, though.  If anyone cares.
2017-06-22 20:42:44 +02:00
bf534010cb _init() -> _make() where possible 2017-06-22 20:42:44 +02:00
7b0d7a19e5 Cleanup 2017-06-14 23:28:44 +02:00
1dcd259d05 Make config_item_clone() static 2017-06-12 08:33:59 +02:00
03894cae45 Add VIM syntax highlight for "config" 2017-06-12 02:48:42 +02:00
412100289e Improve read_line()
One less useless boolean variable.
2017-06-12 02:48:42 +02:00
ec128558a4 MPD client: abort pending tasks 2017-06-04 04:27:10 +02:00
7f7606008d Update README 2017-06-04 00:49:15 +02:00
17322a3686 Make socket_io_try_*() actually use read/write
So that they can be used with pipes.
2017-05-07 09:24:03 +02:00
22edb6d489 Add a warning comment to "poller_fd::closed" 2017-05-06 21:15:03 +02:00
9866675bb7 Fix broken toupper_ascii()
Update copyright years.
2017-05-06 10:55:10 +02:00
e25a880883 Add packaging scripts for Meson
It probably doesn't belong here but I don't feel like creating another
repository for this either yet.
2017-04-30 10:45:23 +02:00
9afcb337ad Fix the WebSocket frame parser 2017-02-06 19:45:03 +01:00
daa900e12f Fix and update LICENSE 2017-02-03 23:03:32 +01:00
4a5929b4ef Travis CI: brevify notifications 2017-02-03 23:03:02 +01:00
084e964286 Fixes to the previous batch of commits 2017-01-23 23:14:04 +01:00
0e08055d6d Rename strv_add*() to strv_append*()
Consistency.
2017-01-23 23:07:24 +01:00
6642bdf9cd Rename str_ensure_space() to str_reserve()
Let's not invent our own terminology.
2017-01-23 23:05:42 +01:00
349a0fc3b1 join_strv() -> strv_join(), take a string argument 2017-01-23 23:03:46 +01:00
5552ce1dbe Rename "struct str_vector" to "struct strv"
Short names for things used often.
2017-01-23 23:01:20 +01:00
680980632d Add a library with TUI helpers 2017-01-23 22:55:46 +01:00
973a4b7656 Add ARRAY convenience macros
Because dynamically allocated arrays in C are a pain.
2017-01-23 22:47:39 +01:00
74b00a921a MPD client: fix resource leak 2017-01-18 16:34:25 +01:00
f53b717f3b Add some consts to function arguments 2016-10-23 13:35:16 +02:00
dfc7ff57ef MPD client: support command_list_ok_begin 2016-10-13 01:12:34 +02:00
2a15b1de70 Import an MPD client interface 2016-10-11 09:37:22 +02:00
dc54db9069 Make error_set() return NULL for convenience
This often eliminates the need for a block where one line is enough.
2016-10-10 07:43:57 +02:00
0b77bdeaf9 Merge cstr_split() and cstr_split_ignore_empty() 2016-10-09 09:45:27 +02:00
a34ce0b6b8 Allow ignoring params in http_parse_media_type() 2016-10-09 09:43:46 +02:00
ad143fd8c0 Add some constness to str_map methods 2016-10-05 05:31:39 +02:00
296cc704a1 Fix a comment 2016-10-04 19:12:30 +02:00
c1c191717f Use CLOCK_MONOTONIC_RAW for timers if available
Should make timers work more precisely on Linux.
2016-10-04 15:46:20 +02:00
952cf985dc Add config_read_from_file() 2016-10-01 04:36:59 +02:00
6234f686e0 Add socket_io_*() functions 2016-10-01 04:35:12 +02:00
b07d9df5fc Simplify resolve_relative_filename_generic() 2016-10-01 04:10:44 +02:00
3cc975bb2a Fix Travis CI notifications 2016-03-14 20:51:29 +01:00
365aed456e Add poller_post_fork() for *BSD kqueue 2016-03-13 16:41:16 +01:00
1051ad555a Update README 2016-03-13 16:40:49 +01:00
9bff16f5ec Fix config schema application 2016-02-01 21:51:45 +01:00
052d2ffc9a Allow fsync() to fail on special files 2016-01-26 21:35:45 +01:00
ce8703cea0 Silence warning 2016-01-26 21:35:40 +01:00
f213a76ad4 Add lock_pid_file() 2016-01-17 04:39:19 +01:00
61ca0c988f Get rid of struct typedefs 2016-01-17 04:33:58 +01:00
8a9a28231b Cleanup 2016-01-16 06:19:16 +01:00
ff046ea596 Shuffle code 2016-01-16 06:12:19 +01:00
38d105dede Don't crash when new threads can't be created 2016-01-16 06:12:19 +01:00
a90aeaf0d9 Use a more generic API in write_file{,_safe}() 2016-01-15 22:52:30 +01:00
60dd23ab8f Make writing files a bit safer
Especially configuration files.
2016-01-13 00:38:54 +01:00
9e3cb2b6aa Allow arbitrary configuration keys
So that if the application decides to use them, neither writing nor
loading of the configuration doesn't brutally fail.
2016-01-09 05:21:49 +01:00
f90cc1e5a2 Copy user_data in config_item_set_from
So that the validation callback can make use of it.

This is analogous to the change in schema application.
2016-01-09 05:19:43 +01:00
bc7e83137e Fix usage of freeaddrinfo() in connector 2016-01-04 00:58:57 +01:00
8c06ec3276 Fix the kqueue poller backend 2016-01-04 00:54:27 +01:00
6a19b51516 Fix build of tests under BSD 2016-01-04 00:47:12 +01:00
3f20b39b71 Add a test for connector 2016-01-03 23:35:21 +01:00
385b3bdb47 Fix some leaks in tests 2016-01-03 23:35:21 +01:00
020a11a5b5 Update copyright years 2016-01-03 23:35:21 +01:00
9957adc458 Add a test for the async framework 2016-01-03 23:35:21 +01:00
4dfd88c2c8 Add a CONTAINER_OF macro 2016-01-03 23:35:21 +01:00
b5724a654a Compile tests with pthreads support 2016-01-03 23:35:21 +01:00
66340e08d7 Rewrite connector to use asynchronous getaddrinfo 2016-01-03 23:35:21 +01:00
733de7bae2 Add asynchronous getnameinfo() 2016-01-02 04:36:17 +01:00
13d04e7a35 Add asynchronous getaddrinfo() 2016-01-02 04:36:17 +01:00
455f2cec82 Add an async job manager to the poller 2016-01-02 04:36:17 +01:00
ee40af0031 Add a framework for asynchronous jobs 2016-01-02 04:36:17 +01:00
80815519b3 Pass hostname in connector's success callback 2016-01-01 20:14:16 +01:00
f6d74544f8 Stylistic, equivalent change 2015-12-28 04:03:34 +01:00
2d8a8e0b1b Make config_schema_initialize_item more useful
By also allowing it to set the user_data member.
2015-12-28 04:03:34 +01:00
a4313ee4b9 Constify str_map_iter 2015-12-28 04:03:34 +01:00
8b2e41ed8f Add read_file() and write_file()
And refactor the simple configuration module a bit.
2015-12-13 22:12:38 +01:00
91fca5cb05 Import configuration module from uirc3
It seems to be mature enough, and it's just a bit too useful.
2015-12-11 02:52:13 +01:00
51663d5ee3 Run tests in different processes
So that one broken test doesn't cause the rest to be effectively skipped.
2015-12-10 23:04:37 +01:00
5d3e911f01 Add a test for utf8_iter 2015-12-10 19:54:45 +01:00
75d063e363 Fix a nasty bug in utf8_next()
Uppercase ASCII was read incorrectly.
2015-12-10 19:54:45 +01:00
122ab355a6 Fix running tests on Windows etc. 2015-12-10 19:54:45 +01:00
0adcaf67c2 Path searching changes
- fixed XDG config file search (didn't include /etc/xdg)
 - added XDG data file search
 - added a generic function to build custom resolvers
 - code reshuffled
2015-11-19 13:27:12 +01:00
835f0a36db Convert README to AsciiDoc 2015-09-27 01:55:27 +02:00
6b1273f43c Travis CI: Update IRC server address 2015-08-22 21:14:41 +02:00
bb3d669c3b Fix epoll with no FDs to watch over 2015-08-22 20:59:35 +02:00
ee2457df7c Fix an embarrassing bug in msg_unpacker 2015-08-17 01:53:49 +02:00
21 changed files with 4496 additions and 791 deletions

View File

@@ -1,18 +0,0 @@
language: c
notifications:
irc:
channels: "anathema.us.nu#anathema"
use_notice: true
skip_join: true
compiler:
- clang
- gcc
before_install:
- sudo apt-get update -qq
before_script:
- mkdir build
- cd build
script:
- cmake .. -DCMAKE_INSTALL_PREFIX=/usr
- make
- ctest -V

View File

@@ -2,33 +2,45 @@ project (liberty C)
cmake_minimum_required (VERSION 2.8.5)
# Moar warnings
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# -Wunused-function is pretty annoying here, as everything is static
set (CMAKE_C_FLAGS "-std=c99 -Wall -Wextra -Wno-unused-function")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
set (wdisabled "-Wno-unused-function -Wno-implicit-fallthrough")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# Dependencies
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
include (AddThreads)
find_package (PkgConfig REQUIRED)
pkg_check_modules (libssl REQUIRED libssl libcrypto)
# -lpthread is only there for debugging (gdb & errno)
# -lrt is only for glibc < 2.17
set (common_libraries ${libssl_LIBRARIES} rt pthread)
if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
include_directories (/usr/local/include)
link_directories (/usr/local/lib)
# Our POSIX version macros make these undefined
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
set (common_libraries ${libssl_LIBRARIES})
include_directories (${libssl_INCLUDE_DIRS})
link_directories (${libssl_LIBRARY_DIRS})
# Generate a configuration file
# TODO: actualy use the configuration file for something; so far we allow
# for direct inclusion without running this CMakeLists.txt
configure_file (${PROJECT_SOURCE_DIR}/liberty-config.h.in
${PROJECT_BINARY_DIR}/liberty-config.h)
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
set (common_sources ${PROJECT_BINARY_DIR}/liberty-config.h)
# -lrt is only for glibc < 2.17
# -liconv may or may not be a part of libc
foreach (extra iconv rt)
find_library (extra_lib_${extra} ${extra})
if (extra_lib_${extra})
list (APPEND common_libraries ${extra})
endif (extra_lib_${extra})
endforeach (extra)
# Build some unit tests
include_directories (${PROJECT_SOURCE_DIR})
enable_testing ()
foreach (name liberty proto)
add_executable (test-${name} tests/${name}.c ${common_sources})
add_threads (test-${name})
target_link_libraries (test-${name} ${common_libraries})
add_test (test-${name} test-${name})
add_test (NAME test-${name} COMMAND test-${name})
endforeach (name)

22
LICENSE
View File

@@ -1,14 +1,12 @@
Copyright (c) 2014 - 2015, Přemysl Janouch <p.janouch@gmail.com>
All rights reserved.
Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,7 +1,7 @@
liberty
=======
`liberty' is a pseudolibrary of all the common C code I have written for various
'liberty' is a pseudolibrary of all the common C code I have written for various
projects. I used to copy-paste large swaths of code with minimal changes to it
and it slowly became awfully painful to synchronize. The project can be thought
of as a successor to my other C library, libxtnd.
@@ -15,14 +15,17 @@ The API is intentionally unstable, which allows for easy refactoring.
All development is done on Linux, but other POSIX-compatible operating systems
should be supported as well. They have an extremely low priority, however, and
I'm not testing them at all.
I'm not testing them at all, with the exception of OpenBSD.
Contributing and Support
------------------------
Use https://git.janouch.name/p/liberty to report any bugs, request features,
or submit pull requests. `git send-email` is tolerated. If you want to discuss
the project, feel free to join me at ircs://irc.janouch.name, channel #dev.
Bitcoin donations are accepted at: 12r5uEWEgcHC46xd64tt3hHt9EUvYYDHe9
License
-------
`liberty' is written by Přemysl Janouch <p.janouch@gmail.com>.
You may use the software under the terms of the ISC license, the text of which
is included within the package, or, at your option, you may relicense the work
under the MIT or the Modified BSD License, as listed at the following site:
http://www.gnu.org/licenses/license-list.html
This software is released under the terms of the 0BSD license, the text of which
is included within the package along with the list of authors.

23
cmake/AddThreads.cmake Normal file
View File

@@ -0,0 +1,23 @@
# Public Domain
# We're looking for pthreads only, while preferring the -pthread flag
set (CMAKE_THREAD_PREFER_PTHREAD ON)
set (THREADS_PREFER_PTHREAD_FLAG ON)
find_package (Threads)
# Prepares the given target for threads
function (add_threads target)
if (NOT Threads_FOUND OR NOT CMAKE_USE_PTHREADS_INIT)
message (FATAL_ERROR "pthreads not found")
endif (NOT Threads_FOUND OR NOT CMAKE_USE_PTHREADS_INIT)
if (THREADS_HAVE_PTHREAD_ARG)
set_property (TARGET ${target} PROPERTY
COMPILE_OPTIONS "-pthread")
set_property (TARGET ${target} PROPERTY
INTERFACE_COMPILE_OPTIONS "-pthread")
endif (THREADS_HAVE_PTHREAD_ARG)
if (CMAKE_THREAD_LIBS_INIT)
target_link_libraries (${target} "${CMAKE_THREAD_LIBS_INIT}")
endif (CMAKE_THREAD_LIBS_INIT)
endfunction (add_threads)

17
cmake/FindNcursesw.cmake Normal file
View File

@@ -0,0 +1,17 @@
# Public Domain
find_package (PkgConfig REQUIRED)
pkg_check_modules (Ncursesw QUIET ncursesw)
# OpenBSD doesn't provide a pkg-config file
set (required_vars Ncursesw_LIBRARIES)
if (NOT Ncursesw_FOUND)
find_library (Ncursesw_LIBRARIES NAMES ncursesw)
find_path (Ncursesw_INCLUDE_DIRS ncurses.h)
list (APPEND required_vars Ncursesw_INCLUDE_DIRS)
endif (NOT Ncursesw_FOUND)
include (FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS (Ncursesw DEFAULT_MSG ${required_vars})
mark_as_advanced (Ncursesw_LIBRARIES Ncursesw_INCLUDE_DIRS)

10
cmake/FindUnistring.cmake Normal file
View File

@@ -0,0 +1,10 @@
# Public Domain
find_path (Unistring_INCLUDE_DIRS unistr.h)
find_library (Unistring_LIBRARIES NAMES unistring libunistring)
include (FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS (Unistring DEFAULT_MSG
Unistring_INCLUDE_DIRS Unistring_LIBRARIES)
mark_as_advanced (Unistring_LIBRARIES Unistring_INCLUDE_DIRS)

18
fuzz Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
# I'm not sure how to make maximum use of this invention
# Make sure to have llvm-symbolizer installed
clang -g -fsanitize=address,undefined,fuzzer -fno-sanitize-recover=all \
tests/fuzz.c -o fuzz-executor
fuzz () {
echo "`tput bold`-- Fuzzing $1`tput sgr0`"
mkdir -p /tmp/corpus-$1
./fuzz-executor -test=$1 -artifact_prefix=$1- \
-max_total_time=600 -timeout=1 /tmp/corpus-$1
}
if [ $# -gt 0 ]; then
for test in "$@"; do fuzz $test; done
else
for test in $(./fuzz-executor); do fuzz $test; done
fi

View File

File diff suppressed because it is too large Load Diff

270
liberty-tui.c Normal file
View File

@@ -0,0 +1,270 @@
/*
* liberty-tui.c: the ultimate C unlibrary: TUI
*
* Copyright (c) 2016 - 2017, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
// This file includes some common stuff to build TUI applications with
#include <ncurses.h>
// It is surprisingly hard to find a good library to handle Unicode shenanigans,
// and there's enough of those for it to be impractical to reimplement them.
//
// GLib ICU libunistring utf8proc
// Decently sized . . x x
// Grapheme breaks . x . x
// Character width x . x x
// Locale handling . . x .
// Liberal license . x . x
//
// Also note that the ICU API is icky and uses UTF-16 for its primary encoding.
//
// Currently we're chugging along with libunistring but utf8proc seems viable.
// Non-Unicode locales can mostly be handled with simple iconv like in sdtui.
// Similarly grapheme breaks can be guessed at using character width (a basic
// test here is Zalgo text).
//
// None of this is ever going to work too reliably anyway because terminals
// and Unicode don't go awfully well together. In particular, character cell
// devices have some problems with double-wide characters.
#include <unistr.h>
#include <uniwidth.h>
#include <uniconv.h>
#include <unicase.h>
// --- Configurable display attributes -----------------------------------------
struct attrs
{
short fg; ///< Foreground colour index
short bg; ///< Background colour index
chtype attrs; ///< Other attributes
};
/// Decode attributes in the value using a subset of the git config format,
/// ignoring all errors since it doesn't affect functionality
static struct attrs
attrs_decode (const char *value)
{
struct strv v = strv_make ();
cstr_split (value, " ", true, &v);
int colors = 0;
struct attrs attrs = { -1, -1, 0 };
for (char **it = v.vector; *it; it++)
{
char *end = NULL;
long n = strtol (*it, &end, 10);
if (*it != end && !*end && n >= SHRT_MIN && n <= SHRT_MAX)
{
if (colors == 0) attrs.fg = n;
if (colors == 1) attrs.bg = n;
colors++;
}
else if (!strcmp (*it, "bold")) attrs.attrs |= A_BOLD;
else if (!strcmp (*it, "dim")) attrs.attrs |= A_DIM;
else if (!strcmp (*it, "ul")) attrs.attrs |= A_UNDERLINE;
else if (!strcmp (*it, "blink")) attrs.attrs |= A_BLINK;
else if (!strcmp (*it, "reverse")) attrs.attrs |= A_REVERSE;
#ifdef A_ITALIC
else if (!strcmp (*it, "italic")) attrs.attrs |= A_ITALIC;
#endif // A_ITALIC
}
strv_free (&v);
return attrs;
}
// --- Terminal output ---------------------------------------------------------
// Necessary abstraction to simplify aligned, formatted character output
// This callback you need to implement in the application
static bool app_is_character_in_locale (ucs4_t ch);
struct row_char
{
ucs4_t c; ///< Unicode codepoint
chtype attrs; ///< Special attributes
int width; ///< How many cells this takes
};
struct row_buffer
{
ARRAY (struct row_char, chars) ///< Characters
int total_width; ///< Total width of all characters
};
static struct row_buffer
row_buffer_make (void)
{
struct row_buffer self = {};
ARRAY_INIT_SIZED (self.chars, 256);
return self;
}
static void
row_buffer_free (struct row_buffer *self)
{
free (self->chars);
}
/// Replace invalid chars and push all codepoints to the array w/ attributes.
static void
row_buffer_append (struct row_buffer *self, const char *str, chtype attrs)
{
// The encoding is only really used internally for some corner cases
const char *encoding = locale_charset ();
// Note that this function is a hotspot, try to keep it decently fast
struct row_char current = { .attrs = attrs };
struct row_char invalid = { .attrs = attrs, .c = '?', .width = 1 };
const uint8_t *next = (const uint8_t *) str;
while ((next = u8_next (&current.c, next)))
{
current.width = uc_width (current.c, encoding);
if (current.width < 0 || !app_is_character_in_locale (current.c))
current = invalid;
ARRAY_RESERVE (self->chars, 1);
self->chars[self->chars_len++] = current;
self->total_width += current.width;
}
}
static void
row_buffer_append_args (struct row_buffer *self, const char *s, ...)
ATTRIBUTE_SENTINEL;
static void
row_buffer_append_args (struct row_buffer *self, const char *s, ...)
{
va_list ap;
va_start (ap, s);
while (s)
{
row_buffer_append (self, s, va_arg (ap, chtype));
s = va_arg (ap, const char *);
}
va_end (ap);
}
static void
row_buffer_append_buffer (struct row_buffer *self, const struct row_buffer *rb)
{
ARRAY_RESERVE (self->chars, rb->chars_len);
memcpy (self->chars + self->chars_len, rb->chars,
rb->chars_len * sizeof *rb->chars);
self->chars_len += rb->chars_len;
self->total_width += rb->total_width;
}
/// Pop as many codepoints as needed to free up "space" character cells.
/// Given the suffix nature of combining marks, this should work pretty fine.
static int
row_buffer_pop_cells (struct row_buffer *self, int space)
{
int made = 0;
while (self->chars_len && made < space)
made += self->chars[--self->chars_len].width;
self->total_width -= made;
return made;
}
static void
row_buffer_space (struct row_buffer *self, int width, chtype attrs)
{
if (width < 0)
return;
ARRAY_RESERVE (self->chars, (size_t) width);
struct row_char space = { .attrs = attrs, .c = ' ', .width = 1 };
self->total_width += width;
while (width-- > 0)
self->chars[self->chars_len++] = space;
}
static void
row_buffer_ellipsis (struct row_buffer *self, int target)
{
if (self->total_width <= target
|| !row_buffer_pop_cells (self, self->total_width - target))
return;
// We use attributes from the last character we've removed,
// assuming that we don't shrink the array (and there's no real need)
ucs4_t ellipsis = 0x2026; // …
if (app_is_character_in_locale (ellipsis))
{
if (self->total_width >= target)
row_buffer_pop_cells (self, 1);
if (self->total_width + 1 <= target)
row_buffer_append (self, "", self->chars[self->chars_len].attrs);
}
else if (target >= 3)
{
if (self->total_width >= target)
row_buffer_pop_cells (self, 3);
if (self->total_width + 3 <= target)
row_buffer_append (self, "...", self->chars[self->chars_len].attrs);
}
}
static void
row_buffer_align (struct row_buffer *self, int target, chtype attrs)
{
row_buffer_ellipsis (self, target);
row_buffer_space (self, target - self->total_width, attrs);
}
static void
row_buffer_print (uint32_t *ucs4, chtype attrs)
{
// This assumes that we can reset the attribute set without consequences
char *str = u32_strconv_to_locale (ucs4);
if (str)
{
attrset (attrs);
addstr (str);
attrset (0);
free (str);
}
}
static void
row_buffer_flush (struct row_buffer *self)
{
if (!self->chars_len)
return;
// We only NUL-terminate the chunks because of the libunistring API
uint32_t chunk[self->chars_len + 1], *insertion_point = chunk;
for (size_t i = 0; i < self->chars_len; i++)
{
struct row_char *iter = self->chars + i;
if (i && iter[0].attrs != iter[-1].attrs)
{
row_buffer_print (chunk, iter[-1].attrs);
insertion_point = chunk;
}
*insertion_point++ = iter->c;
*insertion_point = 0;
}
row_buffer_print (chunk, self->chars[self->chars_len - 1].attrs);
}

3107
liberty.c

File diff suppressed because it is too large Load Diff

26
libertyconf.vim Normal file
View File

@@ -0,0 +1,26 @@
" Since the liberty configuration format is nearly indistinguishable,
" this syntax highlight definition needs to be loaded with `set ft=libertyconf`
if exists("b:current_syntax")
finish
endif
syn match libertyconfError "[^_[:alnum:][:space:]]\+"
syn match libertyconfComment "#.*"
syn match libertyconfSpecial "{\|}\|="
syn match libertyconfNumber "[+-]\=\<\d\+\>"
syn match libertyconfBoolean "\c\<\(true\|yes\|on\|false\|no\|off\)\>"
syn match libertyconfNull "null"
syn match libertyconfEscape display "\\\([xX]\x\{1,2}\|\o\{1,3}\|.\|$\)"
\ contained
syn region libertyconfString start=+"+ skip=+\\\\\|\\"+ end=+"+
\ contains=libertyconfEscape
let b:current_syntax = "libertyconf"
hi def link libertyconfError Error
hi def link libertyconfComment Comment
hi def link libertyconfSpecial Special
hi def link libertyconfNumber Number
hi def link libertyconfBoolean Boolean
hi def link libertyconfNull Constant
hi def link libertyconfEscape SpecialChar
hi def link libertyconfString String

28
meson/packaging/make-deb.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/sh -e
cd "$MESON_BUILD_ROOT"
. "$MESON_SUBDIR/meta"
wd="`pwd`/`mktemp -d deb.XXXXXX`"
trap "rm -rf '$wd'" INT QUIT TERM EXIT
[ "$arch" = x86 ] && arch=i386
[ "$arch" = x86_64 ] && arch=amd64
target="$name-$version-$system-$arch.deb"
echo 2.0 > "$wd/debian-binary"
cat > "$wd/control" <<-EOF
Package: $name
Version: $version
Section: misc
Priority: optional
Architecture: $arch
Maintainer: $author
Description: $summary
EOF
fakeroot sh -e <<-EOF
DESTDIR="$wd/pkg" ninja install
cd "$wd/pkg" && tar cJf ../data.tar.xz .
EOF
(cd "$wd" && tar czf control.tar.gz ./control)
ar rc "$target" "$wd/debian-binary" "$wd/control.tar.gz" "$wd/data.tar.xz"
echo Written $target

22
meson/packaging/make-pacman.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/sh -e
cd "$MESON_BUILD_ROOT"
. "$MESON_SUBDIR/meta"
wd="`pwd`/`mktemp -d pacman.XXXXXX`"
trap "rm -rf '$wd'" INT QUIT TERM EXIT
target="$name-$version-$arch.tar.xz"
fakeroot sh -e <<-EOF
DESTDIR="$wd" ninja install
cat > "$wd/.PKGINFO" <<END
pkgname = $name
pkgver = $version-1
pkgdesc = $summary
url = $url
builddate = \`date -u +%s\`
packager = $author
size = \`du -sb | cut -f1\`
arch = $arch
END
cd "$wd" && tar cJf "../$target" .PKGINFO *
echo Written $target
EOF

View File

@@ -0,0 +1,11 @@
# You need to prepare a configuration object with the required metadata
packaging.set ('arch', target_machine.cpu_family ())
packaging.set ('system', target_machine.system ())
configure_file (input: 'meta.in', output: 'meta', configuration: packaging)
# RPM is awful and I've given up on both manual generation (we'd have to either
# include rpmrc data or generate fake noarch packages) and rpmbuild (just no)
run_target ('deb',
command: [join_paths (meson.current_source_dir (), 'make-deb.sh')])
run_target ('pacman',
command: [join_paths (meson.current_source_dir (), 'make-pacman.sh')])

8
meson/packaging/meta.in Normal file
View File

@@ -0,0 +1,8 @@
define() { [ -z "$2" ] && { echo $1 is undefined; exit 1; } || eval "$1='$2'"; }
define name "@name@"
define version "@version@"
define summary "@summary@"
define author "@author@"
define arch "@arch@"
define system "@system@"

View File

@@ -61,13 +61,13 @@ siphash (const unsigned char key[16], const unsigned char *m, size_t len)
switch (len - blocks)
{
case 7: last7 |= (uint64_t) m[i + 6] << 48;
case 6: last7 |= (uint64_t) m[i + 5] << 40;
case 5: last7 |= (uint64_t) m[i + 4] << 32;
case 4: last7 |= (uint64_t) m[i + 3] << 24;
case 3: last7 |= (uint64_t) m[i + 2] << 16;
case 2: last7 |= (uint64_t) m[i + 1] << 8;
case 1: last7 |= (uint64_t) m[i + 0] ;
case 7: last7 |= (uint64_t) m[i + 6] << 48; // Fall-through
case 6: last7 |= (uint64_t) m[i + 5] << 40; // Fall-through
case 5: last7 |= (uint64_t) m[i + 4] << 32; // Fall-through
case 4: last7 |= (uint64_t) m[i + 3] << 24; // Fall-through
case 3: last7 |= (uint64_t) m[i + 2] << 16; // Fall-through
case 2: last7 |= (uint64_t) m[i + 1] << 8; // Fall-through
case 1: last7 |= (uint64_t) m[i + 0] ; // Fall-through
default:;
};
v3 ^= last7;

297
tests/fuzz.c Normal file
View File

@@ -0,0 +1,297 @@
/*
* tests/fuzz.c
*
* Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#define PROGRAM_NAME "fuzz"
#define PROGRAM_VERSION "0"
#define LIBERTY_WANT_SSL
// The MPD client is a full wrapper and needs the network
#define LIBERTY_WANT_POLLER
#define LIBERTY_WANT_ASYNC
#define LIBERTY_WANT_PROTO_IRC
#define LIBERTY_WANT_PROTO_HTTP
#define LIBERTY_WANT_PROTO_SCGI
#define LIBERTY_WANT_PROTO_FASTCGI
#define LIBERTY_WANT_PROTO_WS
#define LIBERTY_WANT_PROTO_MPD
#include "../liberty.c"
#include "../liberty-tui.c"
static bool
app_is_character_in_locale (ucs4_t ch)
{
return ch < 128;
}
// --- UTF-8 -------------------------------------------------------------------
static void
test_utf8_validate (const uint8_t *data, size_t size)
{
utf8_validate ((const char *) data, size);
}
// --- Base 64 -----------------------------------------------------------------
static void
test_base64_decode (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
struct str out = str_make ();
base64_decode (wrap.str, true /* ignore_ws */, &out);
str_free (&out);
str_free (&wrap);
}
// --- IRC ---------------------------------------------------------------------
static void
test_irc_parse_message (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
struct irc_message msg;
irc_parse_message (&msg, wrap.str);
irc_free_message (&msg);
str_free (&wrap);
}
// --- HTTP --------------------------------------------------------------------
static void
test_http_parse_media_type (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
char *type = NULL;
char *subtype = NULL;
struct str_map parameters = str_map_make (free);
http_parse_media_type (wrap.str, &type, &subtype, &parameters);
free (type);
free (subtype);
str_map_free (&parameters);
str_free (&wrap);
}
static void
test_http_parse_upgrade (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
struct http_protocol *protocols = NULL;
http_parse_upgrade (wrap.str, &protocols);
LIST_FOR_EACH (struct http_protocol, iter, protocols)
http_protocol_destroy (iter);
str_free (&wrap);
}
// --- SCGI --------------------------------------------------------------------
static bool
test_scgi_parser_on_headers_read (void *user_data)
{
(void) user_data;
return true;
}
static bool
test_scgi_parser_on_content (void *user_data, const void *data, size_t len)
{
(void) user_data;
(void) data;
(void) len;
return true;
}
static void
test_scgi_parser_push (const uint8_t *data, size_t size)
{
struct scgi_parser parser = scgi_parser_make ();
parser.on_headers_read = test_scgi_parser_on_headers_read;
parser.on_content = test_scgi_parser_on_content;
scgi_parser_push (&parser, data, size, NULL);
scgi_parser_free (&parser);
}
// --- WebSockets --------------------------------------------------------------
static bool
test_ws_parser_on_frame_header (void *user_data, const struct ws_parser *self)
{
(void) user_data;
(void) self;
return true;
}
static bool
test_ws_parser_on_frame (void *user_data, const struct ws_parser *self)
{
(void) user_data;
(void) self;
return true;
}
static void
test_ws_parser_push (const uint8_t *data, size_t size)
{
struct ws_parser parser = ws_parser_make ();
parser.on_frame_header = test_ws_parser_on_frame_header;
parser.on_frame = test_ws_parser_on_frame;
ws_parser_push (&parser, data, size);
ws_parser_free (&parser);
}
// --- FastCGI -----------------------------------------------------------------
static bool
test_fcgi_parser_on_message (const struct fcgi_parser *parser, void *user_data)
{
(void) parser;
(void) user_data;
return true;
}
static void
test_fcgi_parser_push (const uint8_t *data, size_t size)
{
struct fcgi_parser parser = fcgi_parser_make ();
parser.on_message = test_fcgi_parser_on_message;
fcgi_parser_push (&parser, data, size);
fcgi_parser_free (&parser);
}
static void
test_fcgi_nv_parser_push (const uint8_t *data, size_t size)
{
struct str_map values = str_map_make (free);
struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make ();
nv_parser.output = &values;
fcgi_nv_parser_push (&nv_parser, data, size);
fcgi_nv_parser_free (&nv_parser);
str_map_free (&values);
}
// --- Config ------------------------------------------------------------------
static void
test_config_item_parse (const uint8_t *data, size_t size)
{
struct config_item *item =
config_item_parse ((const char *) data, size, false, NULL);
if (item)
config_item_destroy (item);
}
// --- TUI ---------------------------------------------------------------------
static void
test_attrs_decode (const uint8_t *data, size_t size)
{
struct str wrap = str_make ();
str_append_data (&wrap, data, size);
attrs_decode (wrap.str);
str_free (&wrap);
}
// --- MPD ---------------------------------------------------------------------
static void
test_mpd_client_process_input (const uint8_t *data, size_t size)
{
struct poller poller;
poller_init (&poller);
struct mpd_client mpd = mpd_client_make (&poller);
str_append_data (&mpd.read_buffer, data, size);
mpd_client_process_input (&mpd);
mpd_client_free (&mpd);
poller_free (&poller);
}
// --- Main --------------------------------------------------------------------
typedef void (*fuzz_test_fn) (const uint8_t *data, size_t size);
static fuzz_test_fn generator = NULL;
void
LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
{
generator (data, size);
}
int
LLVMFuzzerInitialize (int *argcp, char ***argvp)
{
struct str_map targets = str_map_make (NULL);
#define REGISTER(name) str_map_set (&targets, #name, test_ ## name);
REGISTER (utf8_validate)
REGISTER (base64_decode)
REGISTER (irc_parse_message)
REGISTER (http_parse_media_type)
REGISTER (http_parse_upgrade)
REGISTER (scgi_parser_push)
REGISTER (ws_parser_push)
REGISTER (fcgi_parser_push)
REGISTER (fcgi_nv_parser_push)
REGISTER (config_item_parse)
REGISTER (attrs_decode)
REGISTER (mpd_client_process_input)
char **argv = *argvp, *option = "-test=", *name = NULL;
for (int i = 1; i < *argcp; i++)
if (!strncmp (argv[i], option, strlen (option)))
{
name = argv[i] + strlen (option);
memmove (argv + i, argv + i + 1, (*argcp - i) * sizeof *argv);
(*argcp)--;
}
if (!name)
{
struct str_map_iter iter = str_map_iter_make (&targets);
while (str_map_iter_next (&iter))
printf ("%s\n", iter.link->key);
exit (EXIT_FAILURE);
}
if (!(generator = str_map_find (&targets, name)))
{
fprintf (stderr, "Unknown test: %s\n", name);
exit (EXIT_FAILURE);
}
str_map_free (&targets);
return 0;
}

View File

@@ -1,12 +1,10 @@
/*
* tests/liberty.c
*
* Copyright (c) 2015, Přemysl Janouch <p.janouch@gmail.com>
* All rights reserved.
* Copyright (c) 2015 - 2016, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -21,6 +19,9 @@
#define PROGRAM_NAME "test"
#define PROGRAM_VERSION "0"
#define LIBERTY_WANT_POLLER
#define LIBERTY_WANT_ASYNC
#include "../liberty.c"
// --- Memory ------------------------------------------------------------------
@@ -101,10 +102,10 @@ test_list (void)
}
// Remove a few entries
LIST_UNLINK (list, a[0]); a[0] = NULL;
LIST_UNLINK (list, a[3]); a[3] = NULL;
LIST_UNLINK (list, a[4]); a[4] = NULL;
LIST_UNLINK (list, a[6]); a[6] = NULL;
LIST_UNLINK (list, a[0]); free (a[0]); a[0] = NULL;
LIST_UNLINK (list, a[3]); free (a[3]); a[3] = NULL;
LIST_UNLINK (list, a[4]); free (a[4]); a[4] = NULL;
LIST_UNLINK (list, a[6]); free (a[6]); a[6] = NULL;
// Prepend one more item
a[0] = make_link (0);
@@ -133,11 +134,11 @@ test_list_with_tail (void)
}
// Remove a few entries
LIST_UNLINK_WITH_TAIL (list, tail, a[0]); a[0] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[3]); a[3] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[4]); a[4] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[6]); a[6] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[9]); a[9] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[0]); free (a[0]); a[0] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[3]); free (a[3]); a[3] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[4]); free (a[4]); a[4] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[6]); free (a[6]); a[6] = NULL;
LIST_UNLINK_WITH_TAIL (list, tail, a[9]); free (a[9]); a[9] = NULL;
// Append one more item
a[9] = make_link (9);
@@ -154,31 +155,28 @@ test_list_with_tail (void)
// --- Strings -----------------------------------------------------------------
static void
test_str_vector (void)
test_strv (void)
{
struct str_vector v;
str_vector_init (&v);
str_vector_add_owned (&v, xstrdup ("xkcd"));
str_vector_reset (&v);
struct strv v = strv_make ();
strv_append_owned (&v, xstrdup ("xkcd"));
strv_reset (&v);
const char *a[] =
{ "123", "456", "a", "bc", "def", "ghij", "klmno", "pqrstu" };
// Add the first two items via another vector
struct str_vector w;
str_vector_init (&w);
str_vector_add_args (&w, a[0], a[1], NULL);
str_vector_add_vector (&v, w.vector);
str_vector_free (&w);
struct strv w = strv_make ();
strv_append_args (&w, a[0], a[1], NULL);
strv_append_vector (&v, w.vector);
strv_free (&w);
// Add an item and delete it right after
str_vector_add (&v, "test");
str_vector_remove (&v, v.len - 1);
strv_append (&v, "test");
strv_remove (&v, v.len - 1);
// Add the rest of the list properly
for (int i = 2; i < (int) N_ELEMENTS (a); i++)
str_vector_add (&v, a[i]);
strv_append (&v, a[i]);
// Check the contents
soft_assert (v.len == N_ELEMENTS (a));
@@ -186,7 +184,7 @@ test_str_vector (void)
soft_assert (!strcmp (v.vector[i], a[i]));
soft_assert (v.vector[v.len] == NULL);
str_vector_free (&v);
strv_free (&v);
}
static void
@@ -194,15 +192,13 @@ test_str (void)
{
uint8_t x[] = { 0x12, 0x34, 0x56, 0x78, 0x11, 0x22, 0x33, 0x44 };
struct str s;
str_init (&s);
str_ensure_space (&s, MEGA);
struct str s = str_make ();
str_reserve (&s, MEGA);
str_append_data (&s, x, sizeof x);
str_remove_slice (&s, 4, 4);
soft_assert (s.len == 4);
struct str t;
str_init (&t);
struct str t = str_make ();
str_append_str (&t, &s);
str_append (&t, "abc");
str_append_c (&t, 'd');
@@ -263,10 +259,8 @@ static void
test_str_map (void)
{
// Put two reference counted objects in the map under case-insensitive keys
struct str_map m;
str_map_init (&m);
struct str_map m = str_map_make (free_counter);
m.key_xfrm = tolower_ascii_strxfrm;
m.free = free_counter;
int *a = make_counter ();
int *b = make_counter ();
@@ -280,8 +274,7 @@ test_str_map (void)
soft_assert (str_map_find (&m, "DEFghi") == b);
// Check that we can iterate over both of them
struct str_map_iter iter;
str_map_iter_init (&iter, &m);
struct str_map_iter iter = str_map_iter_make (&m);
bool met_a = false;
bool met_b = false;
@@ -308,8 +301,7 @@ test_str_map (void)
free_counter (b);
// Iterator test with a high number of items
str_map_init (&m);
m.free = free;
m = str_map_make (free);
for (size_t i = 0; i < 100 * 100; i++)
{
@@ -317,8 +309,7 @@ test_str_map (void)
str_map_set (&m, x, x);
}
struct str_map_unset_iter unset_iter;
str_map_unset_iter_init (&unset_iter, &m);
struct str_map_unset_iter unset_iter = str_map_unset_iter_make (&m);
while ((str_map_unset_iter_next (&unset_iter)))
{
unsigned long x;
@@ -335,10 +326,23 @@ test_str_map (void)
static void
test_utf8 (void)
{
const char valid [] = "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm";
const char invalid[] = "\xf0\x90\x28\xbc";
soft_assert ( utf8_validate (valid, sizeof valid));
soft_assert (!utf8_validate (invalid, sizeof invalid));
const char *full = "\xc5\x99", *partial = full, *empty = full;
soft_assert (utf8_decode (&full, 2) == 0x0159);
soft_assert (utf8_decode (&partial, 1) == -2);
soft_assert (utf8_decode (&empty, 0) == -1);
const char valid[] = "2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm";
const char invalid_1[] = "\xf0\x90\x28\xbc";
const char invalid_2[] = "\xc0\x80";
soft_assert ( utf8_validate (valid, sizeof valid));
soft_assert (!utf8_validate (invalid_1, sizeof invalid_1));
soft_assert (!utf8_validate (invalid_2, sizeof invalid_2));
struct utf8_iter iter = utf8_iter_make ("fóọ");
size_t ch_len;
hard_assert (utf8_iter_next (&iter, &ch_len) == 'f' && ch_len == 1);
hard_assert (utf8_iter_next (&iter, &ch_len) == 0x00F3 && ch_len == 2);
hard_assert (utf8_iter_next (&iter, &ch_len) == 0x1ECD && ch_len == 3);
}
static void
@@ -348,8 +352,8 @@ test_base64 (void)
for (size_t i = 0; i < N_ELEMENTS (data); i++)
data[i] = i;
struct str encoded; str_init (&encoded);
struct str decoded; str_init (&decoded);
struct str encoded = str_make ();
struct str decoded = str_make ();
base64_encode (data, sizeof data, &encoded);
soft_assert (base64_decode (encoded.str, false, &decoded));
@@ -360,6 +364,325 @@ test_base64 (void)
str_free (&decoded);
}
// --- Asynchronous jobs -------------------------------------------------------
struct test_async_data
{
struct async_manager manager; ///< Async manager
struct async_getaddrinfo *gai; ///< Address resolution job
struct async_getnameinfo *gni; ///< Name resolution job
struct async busyloop; ///< Busy job for cancellation
bool finished; ///< End of test indicator
};
static void
on_getnameinfo (int err, char *host, char *service, void *user_data)
{
(void) host;
(void) service;
hard_assert (!err);
struct test_async_data *data = user_data;
data->gni = NULL;
async_cancel (&data->busyloop);
}
static void
on_getaddrinfo (int err, struct addrinfo *results, void *user_data)
{
hard_assert (!err);
struct test_async_data *data = user_data;
data->gai = NULL;
data->gni = async_getnameinfo
(&data->manager, results->ai_addr, results->ai_addrlen, 0);
data->gni->dispatcher = on_getnameinfo;
data->gni->user_data = data;
freeaddrinfo (results);
}
static void
on_busyloop_execute (struct async *async)
{
(void) async;
while (true)
sleep (1);
}
static void
on_busyloop_destroy (struct async *async)
{
CONTAINER_OF (async, struct test_async_data, busyloop)->finished = true;
}
static void
test_async (void)
{
struct test_async_data data;
memset (&data, 0, sizeof data);
data.manager = async_manager_make ();
data.busyloop = async_make (&data.manager);
data.busyloop.execute = on_busyloop_execute;
data.busyloop.destroy = on_busyloop_destroy;
async_run (&data.busyloop);
struct addrinfo hints;
memset (&hints, 0, sizeof hints);
hints.ai_socktype = SOCK_STREAM;
// Localhost should be network-independent and instantaneous
data.gai = async_getaddrinfo (&data.manager, "127.0.0.1", "22", &hints);
data.gai->dispatcher = on_getaddrinfo;
data.gai->user_data = &data;
struct pollfd pfd =
{ .events = POLLIN, .fd = data.manager.finished_pipe[0] };
// Eventually the busyloop should get cancelled and stop the loop
while (!data.finished)
{
hard_assert (poll (&pfd, 1, 1000) == 1);
async_manager_dispatch (&data.manager);
}
soft_assert (!data.gai);
soft_assert (!data.gni);
async_manager_free (&data.manager);
}
// --- Connector ---------------------------------------------------------------
// This also happens to test a large part of the poller implementation
#include <arpa/inet.h>
struct test_connector_fixture
{
const char *host; ///< The host we're listening on
int port; ///< The port we're listening on
int listening_fd; ///< Listening FD
struct poller poller; ///< Poller
struct poller_fd listening_event; ///< Listening event
bool quitting; ///< Quit signal for the event loop
};
static void
test_connector_on_client (const struct pollfd *pfd, void *user_data)
{
(void) user_data;
int fd = accept (pfd->fd, NULL, NULL);
if (fd == -1)
{
if (errno == EAGAIN
|| errno == EINTR
|| errno == ECONNABORTED)
return;
exit_fatal ("%s: %s", "accept", strerror (errno));
}
const char message[] = "Hello!\n";
(void) write (fd, message, strlen (message));
xclose (fd);
}
static bool
test_connector_try_bind
(struct test_connector_fixture *self, const char *host, int port)
{
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons ((self->port = port));
sin.sin_addr.s_addr = inet_addr ((self->host = host));
int fd = socket (AF_INET, SOCK_STREAM, 0);
if (fd < 0)
return true;
int yes = 1;
(void) setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes);
if (bind (fd, (struct sockaddr *) &sin, sizeof sin)
|| listen (fd, 10))
{
xclose (fd);
return false;
}
self->listening_fd = fd;
return true;
}
static void
test_connector_fixture_init
(const void *user_data, struct test_connector_fixture *self)
{
(void) user_data;
// Find a free port on localhost in the user range and bind to it
for (int i = 0; i < 1024; i++)
if (test_connector_try_bind (self, "127.0.0.1", 1024 + i))
break;
if (!self->listening_fd)
exit_fatal ("cannot bind to localhost");
// Make it so that we immediately accept all connections
poller_init (&self->poller);
self->listening_event = poller_fd_make (&self->poller, self->listening_fd);
self->listening_event.dispatcher = test_connector_on_client;
self->listening_event.user_data = (poller_fd_fn) self;
poller_fd_set (&self->listening_event, POLLIN);
}
static void
test_connector_fixture_free
(const void *user_data, struct test_connector_fixture *self)
{
(void) user_data;
poller_free (&self->poller);
xclose (self->listening_fd);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
test_connector_on_connected (void *user_data, int socket, const char *hostname)
{
struct test_connector_fixture *self = user_data;
hard_assert (!strcmp (hostname, self->host));
xclose (socket);
self->quitting = true;
}
static void
test_connector_on_failure (void *user_data)
{
(void) user_data;
exit_fatal ("failed to connect to the prepared port");
}
static void
test_connector_on_connecting (void *user_data, const char *address)
{
(void) user_data;
print_debug ("connecting to %s", address);
}
static void
test_connector_on_error (void *user_data, const char *error)
{
(void) user_data;
print_debug ("%s: %s", "connecting failed", error);
}
static void
test_connector (const void *user_data, struct test_connector_fixture *self)
{
(void) user_data;
print_debug ("final target is %s:%d", self->host, self->port);
struct connector connector;
connector_init (&connector, &self->poller);
connector.on_connecting = test_connector_on_connecting;
connector.on_error = test_connector_on_error;
connector.on_connected = test_connector_on_connected;
connector.on_failure = test_connector_on_failure;
connector.user_data = self;
connector_add_target (&connector, ":D", "nonsense");
char *port = xstrdup_printf ("%d", self->port);
connector_add_target (&connector, self->host, port);
free (port);
while (!self->quitting)
poller_run (&self->poller);
connector_free (&connector);
}
// --- Configuration -----------------------------------------------------------
static void
on_test_config_foo_change (struct config_item *item)
{
*(bool *) item->user_data = item->value.boolean;
}
static bool
test_config_validate_nonnegative
(const struct config_item *item, struct error **e)
{
if (item->type == CONFIG_ITEM_NULL)
return true;
hard_assert (item->type == CONFIG_ITEM_INTEGER);
if (item->value.integer >= 0)
return true;
error_set (e, "must be non-negative");
return false;
}
static struct config_schema g_config_test[] =
{
{ .name = "foo",
.comment = "baz",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off",
.on_change = on_test_config_foo_change },
{ .name = "bar",
.type = CONFIG_ITEM_INTEGER,
.validate = test_config_validate_nonnegative,
.default_ = "1" },
{ .name = "foobar",
.type = CONFIG_ITEM_STRING,
.default_ = "\"qux\\x01\"" },
{}
};
static void
test_config_load (struct config_item *subtree, void *user_data)
{
config_schema_apply_to_object (g_config_test, subtree, user_data);
}
static void
test_config (void)
{
struct config config = config_make ();
bool b = true;
config_register_module (&config, "top", test_config_load, &b);
config_load (&config, config_item_object ());
config_schema_call_changed (config.root);
hard_assert (b == false);
struct config_item *invalid = config_item_integer (-1);
hard_assert (!config_item_set_from (config_item_get (config.root,
"top.bar", NULL), invalid, NULL));
config_item_destroy (invalid);
struct str s = str_make ();
config_item_write (config.root, true, &s);
struct config_item *parsed = config_item_parse (s.str, s.len, false, NULL);
hard_assert (parsed);
config_item_destroy (parsed);
str_free (&s);
config_free (&config);
}
// --- Main --------------------------------------------------------------------
int
@@ -371,12 +694,19 @@ main (int argc, char *argv[])
test_add_simple (&test, "/memory", NULL, test_memory);
test_add_simple (&test, "/list", NULL, test_list);
test_add_simple (&test, "/list-with-tail", NULL, test_list_with_tail);
test_add_simple (&test, "/str-vector", NULL, test_str_vector);
test_add_simple (&test, "/strv", NULL, test_strv);
test_add_simple (&test, "/str", NULL, test_str);
test_add_simple (&test, "/error", NULL, test_error);
test_add_simple (&test, "/str-map", NULL, test_str_map);
test_add_simple (&test, "/utf-8", NULL, test_utf8);
test_add_simple (&test, "/base64", NULL, test_base64);
test_add_simple (&test, "/async", NULL, test_async);
test_add_simple (&test, "/config", NULL, test_config);
test_add (&test, "/connector", struct test_connector_fixture, NULL,
test_connector_fixture_init,
test_connector,
test_connector_fixture_free);
// TODO: write tests for the rest of the library

View File

@@ -1,12 +1,10 @@
/*
* tests/proto.c
*
* Copyright (c) 2015, Přemysl Janouch <p.janouch@gmail.com>
* All rights reserved.
* Copyright (c) 2015, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
@@ -22,12 +20,16 @@
#define PROGRAM_VERSION "0"
#define LIBERTY_WANT_SSL
// The MPD client is a full wrapper and needs the network
#define LIBERTY_WANT_POLLER
#define LIBERTY_WANT_ASYNC
#define LIBERTY_WANT_PROTO_IRC
#define LIBERTY_WANT_PROTO_HTTP
#define LIBERTY_WANT_PROTO_SCGI
#define LIBERTY_WANT_PROTO_FASTCGI
#define LIBERTY_WANT_PROTO_WS
#define LIBERTY_WANT_PROTO_MPD
#include "../liberty.c"
@@ -40,8 +42,7 @@ test_irc (void)
irc_parse_message (&msg, "@first=a\\:\\s\\r\\n\\\\;2nd "
":srv hi there :good m8 :how are you?");
struct str_map_iter iter;
str_map_iter_init (&iter, &msg.tags);
struct str_map_iter iter = str_map_iter_make (&msg.tags);
soft_assert (msg.tags.len == 2);
char *value;
@@ -76,8 +77,7 @@ test_irc (void)
static void
test_http_parser (void)
{
struct str_map parameters;
str_map_init (&parameters);
struct str_map parameters = str_map_make (free);
parameters.key_xfrm = tolower_ascii_strxfrm;
char *type = NULL;
@@ -88,9 +88,11 @@ test_http_parser (void)
soft_assert (!strcasecmp_ascii (subtype, "html"));
soft_assert (parameters.len == 1);
soft_assert (!strcmp (str_map_find (&parameters, "charset"), "utf-8"));
free (type);
free (subtype);
str_map_free (&parameters);
struct http_protocol *protocols;
struct http_protocol *protocols = NULL;
soft_assert (http_parse_upgrade ("websocket, HTTP/2.0, , ", &protocols));
soft_assert (!strcmp (protocols->name, "websocket"));
@@ -105,10 +107,20 @@ test_http_parser (void)
http_protocol_destroy (iter);
}
struct scgi_fixture
{
struct scgi_parser parser;
bool seen_headers;
bool seen_content;
};
static bool
test_scgi_parser_on_headers_read (void *user_data)
{
struct scgi_parser *parser = user_data;
struct scgi_fixture *fixture = user_data;
struct scgi_parser *parser = &fixture->parser;
fixture->seen_headers = true;
soft_assert (parser->headers.len == 4);
soft_assert (!strcmp (str_map_find (&parser->headers,
"CONTENT_LENGTH"), "27"));
@@ -124,7 +136,9 @@ test_scgi_parser_on_headers_read (void *user_data)
static bool
test_scgi_parser_on_content (void *user_data, const void *data, size_t len)
{
(void) user_data;
struct scgi_fixture *fixture = user_data;
fixture->seen_content = true;
soft_assert (!strncmp (data, "What is the answer to life?", len));
return true;
}
@@ -132,11 +146,12 @@ test_scgi_parser_on_content (void *user_data, const void *data, size_t len)
static void
test_scgi_parser (void)
{
struct scgi_parser parser;
scgi_parser_init (&parser);
parser.on_headers_read = test_scgi_parser_on_headers_read;
parser.on_content = test_scgi_parser_on_content;
parser.user_data = &parser;
struct scgi_fixture fixture = { scgi_parser_make(), false, false };
struct scgi_parser *parser = &fixture.parser;
parser->on_headers_read = test_scgi_parser_on_headers_read;
parser->on_content = test_scgi_parser_on_content;
parser->user_data = &fixture;
// This is an example straight from the specification
const char example[] =
@@ -148,8 +163,9 @@ test_scgi_parser (void)
","
"What is the answer to life?";
soft_assert (scgi_parser_push (&parser, example, sizeof example, NULL));
scgi_parser_free (&parser);
soft_assert (scgi_parser_push (parser, example, sizeof example, NULL));
soft_assert (fixture.seen_headers && fixture.seen_content);
scgi_parser_free (parser);
}
static bool
@@ -178,8 +194,7 @@ test_websockets (void)
soft_assert (!strcmp (accept, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="));
free (accept);
struct ws_parser parser;
ws_parser_init (&parser);
struct ws_parser parser = ws_parser_make ();
parser.on_frame_header = test_websockets_on_frame_header;
parser.on_frame = test_websockets_on_frame;
parser.user_data = &parser;
@@ -201,7 +216,7 @@ main (int argc, char *argv[])
test_add_simple (&test, "/http-parser", NULL, test_http_parser);
test_add_simple (&test, "/scgi-parser", NULL, test_scgi_parser);
test_add_simple (&test, "/websockets", NULL, test_websockets);
// TODO: test FastCGI
// TODO: test FastCGI and MPD
return test_run (&test);
}