From: Nikiroo Date: Sun, 27 Feb 2022 22:36:03 +0000 (+0100) Subject: more utils, more tests (not all tested yet) X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=218ee4a2e44a80e03e746b456f656fcade85731a;p=nsub.git more utils, more tests (not all tested yet) --- diff --git a/src/tests.d b/src/tests.d index d04a965..252cc66 100644 --- a/src/tests.d +++ b/src/tests.d @@ -2,7 +2,6 @@ CFLAGS += -Wall -pedantic -I./ -I ../ -std=c99 CXXFLAGS += -Wall -pedantic -I./ -I ../ -LDFLAGS += -lcheck ifdef DEBUG CFLAGS += -ggdb -O0 diff --git a/src/tests/launcher.c b/src/tests/launcher.c index fe34be1..6d31358 100644 --- a/src/tests/launcher.c +++ b/src/tests/launcher.c @@ -1,3 +1,22 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2022 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include #include #include diff --git a/src/tests/launcher.h b/src/tests/launcher.h index 3d28250..750a842 100644 --- a/src/tests/launcher.h +++ b/src/tests/launcher.h @@ -15,19 +15,19 @@ END_TEST\ #define FAIL(...) \ ck_abort_msg(__VA_ARGS__)\ -#define ASSERT_EQUALS_STR(title, un, deux) \ - if (strcmp(un, deux)) { \ -ck_abort_msg("%s\n\tExpected: <%s>\n\tReceived: <%s>", title, un, deux); \ +#define ASSERT_EQUALS_STR(title, expected, received) \ + if (strcmp(expected, received)) { \ +ck_abort_msg("%s\n\tExpected: <%s>\n\tReceived: <%s>", title, expected, received); \ } -#define ASSERT_EQUALS_INT(title, un, deux) \ - if (un != deux) { \ -ck_abort_msg("%s\n\tExpected: %d\n\tReceived: %d", title, un, deux); \ +#define ASSERT_EQUALS_INT(title, expected, received) \ + if (expected != received) { \ +ck_abort_msg("%s\n\tExpected: %d\n\tReceived: %d", title, expected, received); \ } -#define ASSERT_EQUALS_SIZE(title, un, deux) \ - if (un != deux) { \ -ck_abort_msg("%s\n\tExpected: %zu\n\tReceived: %zu", title, un, deux); \ +#define ASSERT_EQUALS_SIZE(title, expected, received) \ + if (expected != received) { \ +ck_abort_msg("%s\n\tExpected: %zu\n\tReceived: %zu", title, expected, received); \ } SRunner *get_tests(int more); diff --git a/src/tests/utils.d b/src/tests/utils.d index 1a530fc..6ab01fc 100644 --- a/src/tests/utils.d +++ b/src/tests/utils.d @@ -3,6 +3,9 @@ CFLAGS += -Wall -pedantic -I./ -I ../ -std=c99 CXXFLAGS += -Wall -pedantic -I./ -I ../ LDFLAGS += -lcheck +ifdef DEBIAN +LDFLAGS += -lsubunit -lm -lpthread -lrt +endif ifdef DEBUG CFLAGS += -ggdb -O0 diff --git a/src/tests/utils/array.c b/src/tests/utils/array.c new file mode 100644 index 0000000..956d04d --- /dev/null +++ b/src/tests/utils/array.c @@ -0,0 +1,69 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2022 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "launcher.h" +#include "utils/array.h" + +array *a; + +void test_array_setup() { + a = new_array(sizeof(char), 80); +} + +void test_array_teardown() { + free_array(a); + a = NULL; +} + +void array_reset() { + test_array_teardown(); + test_array_setup(); +} + +START(init) + if (!a) + FAIL("new_array returned NULL"); + + if (array_count(a)) + FAIL("empty array has a size of %zu", array_count(a)); + END + +Suite *test_array(const char title[]) { + Suite *suite = suite_create(title); + + TCase *core = tcase_create("core"); + tcase_add_checked_fixture(core, test_array_setup, test_array_teardown); + tcase_add_test(core, init); + + suite_add_tcase(suite, core); + + return suite; +} + +Suite *test_array_more(const char title[]) { + Suite *suite = suite_create(title); + + TCase *tmore = tcase_create("more"); + tcase_add_checked_fixture(tmore, test_array_setup, test_array_teardown); + // TODO + + suite_add_tcase(suite, tmore); + + return suite; +} diff --git a/src/tests/utils/cstring.c b/src/tests/utils/cstring.c new file mode 100644 index 0000000..23e6bcb --- /dev/null +++ b/src/tests/utils/cstring.c @@ -0,0 +1,265 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2022 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "launcher.h" +#include "utils/cstring.h" + +#define TEST_FILE_READLN "utils/test_readln.txt" + +cstring *s; + +void test_cstring_setup() { + s = new_cstring(); +} + +void test_cstring_teardown() { + free_cstring(s); + s = NULL; +} + +void cstring_reset() { + test_cstring_teardown(); + test_cstring_setup(); +} + +START(init) + if (!s) + FAIL("new_cstring returned NULL"); + + if (s->length) + FAIL("empty cstring has a size of %zu", s->length); + END + +START(adds) + char *str; + + str = "testy"; + cstring_add(s, str); + ASSERT_EQUALS_STR("Short string", str, s->string); + cstring_reset(); + + str = "Fanfan entre dans un bar"; + cstring_add(s, str); + ASSERT_EQUALS_STR("Medium string", str, s->string); + cstring_reset(); + + str = "Ligne 1\nLigne 2\nLigne 3, attention 4 = vide\n"; + cstring_add(s, str); + ASSERT_EQUALS_STR("Multi-line", str, s->string); + cstring_reset(); + + str = + "Les accents en français sont bien là et se retrouvent avec une fréquence élevée"; + cstring_add(s, str); + ASSERT_EQUALS_STR("accents", str, s->string); + cstring_reset(); + + str = "cents: ¢, copyright: ©, arrows: →↓↑←"; + cstring_add(s, str); + ASSERT_EQUALS_STR("UTF-8", str, s->string); + cstring_reset(); + + str = "Os iusti meditabitur sapientiam,\n" + "Et lingua\n" + "eius loquetur\n" + "indicium.\n" + "\n" + "Beatus\n" + "vir qui\n" + "suffert tentationem, Quoniqm\n" + "cum probates\n" + "fuerit accipient\n" + "coronam vitae\n" + "\n" + "Kyrie, fons bonitatis.\n" + "Kyrie, ignis divine, eleison.\n" + "\n" + "O quam sancta, quam serena,\n" + "Quam benigma, quam amoena esse Virgo creditur.\n" + "O quam sancta, quam serena,\n" + "Quam benigma, quam amoena,\n" + "O castitatis lilium.\n" + "\n" + "Kyrie, fons bonitatis.\n" + "Kyrie, ignis divine, eleison.\n" + "\n" + "O quam sancta, quam serena,\n" + "Quam benigma, quam amoena,\n" + "O castitatis lilium.\n"; + cstring_add(s, str); + ASSERT_EQUALS_STR("Long, multi-line string", str, s->string); + cstring_reset(); + + END + +START(clear) + if (!s) + FAIL("new_cstring returned NULL"); + + if (s->length) + FAIL("empty cstring has a size of %zu", s->length); + END + +START(addp) + char *str; + + cstring_addp(s, "%d", 42); + ASSERT_EQUALS_STR("Simple int", "42", s->string); + cstring_reset(); + + cstring_addp(s, "%02d", 1); + ASSERT_EQUALS_STR("Leading zero int", "01", s->string); + cstring_reset(); + + cstring_addp(s, "%d", 352646); + ASSERT_EQUALS_STR("Large int", "352646", s->string); + cstring_reset(); + + str = "Simple test string"; + cstring_addp(s, "%s", str); + ASSERT_EQUALS_STR("Simple string", str, s->string); + cstring_reset(); + + cstring_addp(s, "%s", "String 1, "); + str = "String 2"; + cstring_addp(s, "%s", "String 2"); + ASSERT_EQUALS_STR("Cumulative strings", "String 1, String 2", s->string); + cstring_reset(); + + END + +START(readln) + int read; + FILE *testin = fopen(TEST_FILE_READLN, "r"); + if (!testin) + FAIL("Test file not found: test_readln.txt"); + + read = cstring_readline(s, testin); + if (!read) + FAIL("first line should not be last"); + ASSERT_EQUALS_STR("first line incorrect", "ligne 1", s->string); + cstring_reset(); + + read = cstring_readline(s, testin); + if (!read) + FAIL("second line should not be last"); + ASSERT_EQUALS_STR("second line incorrect", "", s->string); + cstring_reset(); + + read = cstring_readline(s, testin); + if (!read) + FAIL("third line should not be last"); + ASSERT_EQUALS_STR("third line incorrect", "ligne 3", s->string); + cstring_reset(); + + if (cstring_readline(s, testin)) { + FAIL("fourth line should not exist"); + } + + END + +START(ends_with) + char *end; + + cstring_add(s, "fichier.ext"); + + end = ".ext"; + if (!cstring_ends_with(s, end)) + FAIL("fichier.ext does not end in %s", end); + + end = ".ex"; + if (cstring_ends_with(s, end)) + FAIL("fichier.ext ends in %s", end); + + end = "aext"; + if (cstring_ends_with(s, end)) + FAIL("fichier.ext does not end in %s", end); + + end = ""; + if (!cstring_ends_with(s, end)) + FAIL("fichier.ext does not end with nothing"); + + END + +START(starts_with) + char *start; + + cstring_add(s, "fichier.ext"); + + start = "fichier"; + if (!cstring_starts_with(s, start, 0)) + FAIL("fichier.ext does not start with %s", start); + + start = "ichier"; + if (cstring_starts_with(s, start, 0)) + FAIL("fichier.ext starts with %s", start); + + start = "afichier"; + if (cstring_starts_with(s, start, 0)) + FAIL("fichier.ext starts with %s", start); + + start = ""; + if (!cstring_starts_with(s, start, 0)) + FAIL("fichier.ext does not start with nothing"); + + start = "chier"; + if (!cstring_starts_with(s, start, 2)) + FAIL("fichier.ext +2 does not start with %s", start); + + END + +START(many_adds) + size_t count = 50000000; + for (size_t i = 0; i < count; i++) { + cstring_add(s, "1234567890"); + } + + ASSERT_EQUALS_SIZE("Lot of adds", count * 10, s->length); + + END + +Suite *test_cstring(const char title[]) { + Suite *suite = suite_create(title); + + TCase *core = tcase_create("core"); + tcase_add_checked_fixture(core, test_cstring_setup, test_cstring_teardown); + tcase_add_test(core, init); + tcase_add_test(core, adds); + tcase_add_test(core, clear); + tcase_add_test(core, addp); + tcase_add_test(core, readln); + tcase_add_test(core, ends_with); + tcase_add_test(core, starts_with); + + suite_add_tcase(suite, core); + + return suite; +} + +Suite *test_cstring_more(const char title[]) { + Suite *suite = suite_create(title); + + TCase *tmore = tcase_create("more"); + tcase_add_checked_fixture(tmore, test_cstring_setup, test_cstring_teardown); + tcase_add_test(tmore, many_adds); + + suite_add_tcase(suite, tmore); + + return suite; +} diff --git a/src/tests/utils/main.c b/src/tests/utils/main.c index 6354957..182401d 100644 --- a/src/tests/utils/main.c +++ b/src/tests/utils/main.c @@ -20,14 +20,25 @@ #include "main.h" #include "launcher.h" +SRunner *runner = NULL; +void add_test(Suite *test); + SRunner *get_tests(int more) { - //TODO: add tests (the code for those is not ready yet) - /* - SRunner *runner = srunner_create(test_cstring("cstring")); + add_test(test_cstring("cstring")); + if (more) + add_test(test_cstring_more("cstring -- more (longer)")); + + add_test(test_array("array")); if (more) - srunner_add_suite(runner, test_cstring_more("more tests (longer)")); + add_test(test_array_more("array -- more (longer)")); return runner; - */ - return NULL; +} + +void add_test(Suite *test) { + if (!runner) { + runner = srunner_create(test); + } else { + srunner_add_suite(runner, test); + } } diff --git a/src/tests/utils/main.h b/src/tests/utils/main.h index fa1b559..497bc2f 100644 --- a/src/tests/utils/main.h +++ b/src/tests/utils/main.h @@ -22,8 +22,9 @@ #include -//TODO: add tests (the code for those is not ready yet) -//Suite *test_cstring(const char title[]); -//Suite *test_cstring_more(const char title[]); +Suite *test_cstring(const char title[]); +Suite *test_cstring_more(const char title[]); +Suite *test_array(const char title[]); +Suite *test_array_more(const char title[]); #endif /* SRC_TESTS_UTILS_MAIN_H_ */ diff --git a/src/tests/utils/test_readln.txt b/src/tests/utils/test_readln.txt new file mode 100644 index 0000000..10f2897 --- /dev/null +++ b/src/tests/utils/test_readln.txt @@ -0,0 +1,3 @@ +ligne 1 + +ligne 3 diff --git a/src/utils/base64.c b/src/utils/base64.c new file mode 100644 index 0000000..cf6657c --- /dev/null +++ b/src/utils/base64.c @@ -0,0 +1,232 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2013 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "base64.h" +#include "cstring.h" + +cstring *base64_decodesi(base64 *self, char *data, size_t len); +//cstring *base64_encodesi(base64 *self, char *data, size_t len); + +char *b64_encode(const char *data, const char *encoding_table, + size_t input_length, + size_t *output_length); + +char *b64_decode(const char *data, const char *decoding_table, + size_t input_length, + size_t *output_length); + +static char b64_encoding_table[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/'}; + +static int b64_mod_table[] = {0, 2, 1}; + +base64 *base64_new() { + base64 *self; + int i; + + self = (base64 *)malloc(sizeof(base64)); + self->table = malloc(256 * sizeof(char)); + + for (i = 0; i < 64; i++) + self->table[(unsigned char)b64_encoding_table[i]] = i; + + return self; +} + +void base64_free(base64 *self) { + if (!self) + return; + + free(self->table); + free(self); +} + +cstring *base64_encode(base64 *self, cstring *data) { + if (!data) + return NULL; + + return base64_encodesi(self, data->string, data->length); +} + +cstring *base64_decode(base64 *self, cstring *data) { + if (!data) + return NULL; + + return base64_decodesi(self, data->string, data->length); +} + +cstring *base64_encodes(base64 *self, char *data) { + size_t len; + + if (!data) + return NULL; + + len = strlen(data); + + return base64_encodesi(self, data, len); +} + +cstring *base64_decodes(base64 *self, char *data) { + size_t len; + + if (data) + len = strlen(data); + + return base64_decodesi(self, data, len); +} + +cstring *base64_encodesi(base64 *self, char *data, size_t len) { + cstring *rep; + char *encoded; + size_t size; + + rep = NULL; + size = 0; + encoded = b64_encode(data, b64_encoding_table, len, &size); + + if (encoded) { + rep = new_cstring(); + free(rep->string); + rep->string = encoded; + rep->length = size; + cstring_compact(rep); + } + + return rep; +} + +cstring *base64_decodesi(base64 *self, char *data, size_t len) { + cstring *rep; + char *decoded; + size_t size; + + rep = NULL; + size = 0; + decoded = b64_decode(data, self->table, len, &size); + + if (decoded) { + rep = new_cstring(); + free(rep->string); + rep->string = decoded; + rep->length = size; + cstring_compact(rep); + } + + return rep; +} + +char *b64_encode(const char *data, const char *encoding_table, + size_t input_length, + size_t *output_length) { + unsigned int i, j; + + *output_length = 4 * ((input_length + 2) / 3) + 1; + + char *encoded_data = malloc(*output_length); + if (encoded_data == NULL) + return NULL; + + for (i = 0, j = 0 ; i < input_length ; ) { + uint32_t octet_a = i < input_length ? + (unsigned char)data[i++] : 0; + uint32_t octet_b = i < input_length ? + (unsigned char)data[i++] : 0; + uint32_t octet_c = i < input_length ? + (unsigned char)data[i++] : 0; + + uint32_t triple = (octet_a << 0x10) + + (octet_b << 0x08) + + octet_c; + + encoded_data[j++] = + (char)encoding_table[(triple >> 3 * 6) & 0x3F]; + encoded_data[j++] = + (char)encoding_table[(triple >> 2 * 6) & 0x3F]; + encoded_data[j++] = + (char)encoding_table[(triple >> 1 * 6) & 0x3F]; + encoded_data[j++] = + (char)encoding_table[(triple >> 0 * 6) & 0x3F]; + } + + for (i = 0 ; (int)i < b64_mod_table[input_length % 3] ; i++) + encoded_data[*output_length - 2 - i] = '='; + encoded_data[*output_length - 1] = '\0'; + + return encoded_data; +} + + +char *b64_decode(const char *data, const char *decoding_table, + size_t input_length, + size_t *output_length) { + + unsigned int i, j; + + if (input_length % 4 != 0) + return NULL; + + *output_length = (input_length / 4 * 3) + 1; + if (data[input_length - 2] == '=') (*output_length)--; + if (data[input_length - 3] == '=') (*output_length)--; + + char *decoded_data = malloc(*output_length); + if (decoded_data == NULL) + return NULL; + + i = j = 0; + for ( ; i < input_length ; i += 4) { + uint32_t sextet_a = 0; + uint32_t sextet_b = 0; + uint32_t sextet_c = 0; + uint32_t sextet_d = 0; + + if (data[i] != '=') { + sextet_a = decoding_table[(unsigned char)data[i + 0]]; + sextet_b = decoding_table[(unsigned char)data[i + 1]]; + sextet_c = decoding_table[(unsigned char)data[i + 2]]; + sextet_d = decoding_table[(unsigned char)data[i + 3]]; + } + + uint32_t triple = (sextet_a << 3 * 6) + + (sextet_b << 2 * 6) + + (sextet_c << 1 * 6) + + (sextet_d << 0 * 6); + + if (j + 1 < *output_length) + decoded_data[j++] = (char)((triple >> 2 * 8) & 0xFF); + if (j + 1 < *output_length) + decoded_data[j++] = (char)((triple >> 1 * 8) & 0xFF); + if (j + 1 < *output_length) + decoded_data[j++] = (char)((triple >> 0 * 8) & 0xFF); + } + + decoded_data[*output_length - 1] = '\0'; + return decoded_data; +} + diff --git a/src/utils/base64.h b/src/utils/base64.h new file mode 100644 index 0000000..7bd9afe --- /dev/null +++ b/src/utils/base64.h @@ -0,0 +1,60 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2013 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef BASE64_H +#define BASE64_H + +#include +#include "cstring.h" + +typedef struct { + char *table; +} base64; + +/** + * Create a new base64 codec. + * + * @return a new codec + */ +base64 *base64_new(); + +/** + * Free the given code. + */ +void base64_free(base64 *self); + +cstring *base64_encode(base64 *self, cstring *data); + +cstring *base64_decode(base64 *self, cstring *data); + +cstring *base64_encodes(base64 *self, char *data); + +cstring *base64_encodesi(base64 *self, char *data, size_t size); + +cstring *base64_decodes(base64 *self, char *data); + +#endif + +#ifdef __cplusplus +} +#endif diff --git a/src/utils/cstring.c b/src/utils/cstring.c new file mode 100644 index 0000000..b0dda84 --- /dev/null +++ b/src/utils/cstring.c @@ -0,0 +1,730 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2013 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + Name: cstring.c + Copyright: niki (cc-by-nc) 2011 + Author: niki + Date: 2011-06-16 + Description: cstring is a collection of helper functions to manipulate string of text + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Windows (and maybe others?) doesn't know about strnlen */ +#ifndef strnlen +size_t strnlen(const char *s, size_t maxlen); +size_t strnlen(const char *s, size_t maxlen) { + size_t i; + for (i = 0; s[i]; i++) { + if (i >= maxlen) + return maxlen; + } + + return i; +} +#endif + +#include "cstring.h" +#include "net.h" +#include "base64.h" + +#ifndef BUFFER_SIZE +#define BUFFER_SIZE 81 +#endif + +#ifdef WIN32 +#define CSTRING_SEP '\\' +#else +#define CSTRING_SEP '/' +#endif + +//start of private prototypes + +struct cstring_private_struct { + size_t buffer_length; +}; + +void cstring_change_case(cstring *self, int up); + +//end of private prototypes + +cstring *new_cstring() { + cstring *string; + + string = malloc(sizeof(cstring)); + strcpy(string->CNAME, "[CString]"); + string->priv = malloc(sizeof(struct cstring_private_struct)); + string->length = 0; + string->priv->buffer_length = BUFFER_SIZE; + string->string = malloc(sizeof(char) * BUFFER_SIZE); + string->string[0] = '\0'; + + return string; +} + +void free_cstring(cstring *string) { + if (!string) + return; + + free(string->priv); + free(string->string); + + free(string); +} + +void cstring_grow(cstring *self, int min) { + cstring_grow_to(self, self->length + min); +} + +void cstring_grow_to(cstring *self, int buffer) { + if (buffer > self->priv->buffer_length) { + self->priv->buffer_length = buffer; + self->string = (char *) realloc(self->string, + sizeof(char) * self->priv->buffer_length); + } +} + +char *cstring_convert(cstring *self) { + char *string; + + if (!self) + return NULL; + + // Note: this could be skipped. + cstring_compact(self); + + free(self->priv); + string = (self->string); + free(self); + + return string; +} + +cstring *cstring_clone(cstring *self) { + if (self == NULL) + return NULL; + + return cstring_clones(self->string); +} + +char *cstring_sclone(cstring *self) { + if (self == NULL) + return NULL; + + return cstring_convert(cstring_clone(self)); +} + +char *cstring_sclones(const char self[]) { + return cstring_convert(cstring_clones(self)); +} + +cstring *cstring_clones(const char self[]) { + cstring *clone; + + if (self == NULL) + return NULL; + + clone = new_cstring(); + cstring_add(clone, self); + + return clone; +} + +void cstring_compact(cstring *self) { + if (self != NULL) { + self->priv->buffer_length = self->length + 1; + self->string = (char *) realloc(self->string, self->length + 1); + } +} + +void cstring_cut_at(cstring *self, size_t size) { + if (self->length > size) { + self->string[size] = '\0'; + self->length = size; + } +} + +/* + clist *cstring_splitc(cstring *self, char delim, char quote) { + clist *list; + cstring *d, *q; + + d = new_cstring(); + q = new_cstring(); + + if (delim) + cstring_add_car(d, delim); + if (quote) + cstring_add_car(q, quote); + + list = cstring_split(self, d, q); + + free_cstring(d); + free_cstring(q); + + return list; + } + + clist *cstring_splits(cstring *self, const char delim[], const char quote[]) { + clist *list; + cstring *d, *q; + + d = new_cstring(); + q = new_cstring(); + + if (delim) + cstring_add(d, delim); + if (quote) + cstring_add(q, quote); + + list = cstring_split(self, d, q); + + free_cstring(d); + free_cstring(q); + + return list; + } + clist *cstring_split(cstring *self, cstring *delim, cstring *quote) { + clist *list; + cstring *elem; + clist_node *node; + size_t i; + int in_quote; + int hasdelim; + + hasdelim = delim && delim->length > 0; + + list = clist_new(); + in_quote = 0; + elem = NULL; + for (i = 0; i < self->length; i++) { + if (quote->length > 0 && cstring_starts_with(self, quote, i)) { + in_quote = !in_quote; + i += quote->length - 1; + } else { + if (elem == NULL) { + elem = new_cstring(); + node = clist_node_new(); + node->data = elem; + node->free_data = free_cstring; + clist_add(list, node); + } + if (!in_quote && hasdelim && cstring_starts_with(self, delim, i)) { + elem = new_cstring(); + node = clist_node_new(); + node->data = elem; + node->free_data = free_cstring; + clist_add(list, node); + i += delim->length - 1; + } else { + cstring_add_car(elem, self->string[i]); + } + } + } + + return list; + } + */ + +cstring *cstring_substring(cstring *self, size_t start, size_t length) { + cstring *sub; + char *source; + + if (length == 0) { + length = self->length - start; + } + + sub = new_cstring(); + source = self->string; + source = source + start; + + cstring_addn(sub, source, length); + + return sub; +} + +int cstring_starts_with(cstring *self, const char find[], size_t start_index) { + return cstring_sstarts_with(self->string, find, start_index); +} + +int cstring_sstarts_with(const char string[], const char find[], + size_t start_index) { + size_t i; + + for (i = 0; + string[start_index + i] == find[i] + && string[start_index + i] != '\0' && find[i] != '\0'; i++) + ; + + return find[i] == '\0'; +} + +int cstring_ends_with(cstring *self, const char find[]) { + size_t sz_needle = strlen(find); + if (sz_needle <= self->length) { + if (!strcmp(self->string + (self->length - sz_needle), find)) + return 1; + } + + return 0; +} + +int cstring_sends_with(const char self[], const char find[]) { + size_t sz = strlen(self); + size_t sz_needle = strlen(find); + if (sz_needle <= sz) { + if (!strcmp(self + (sz - sz_needle), find)) + return 1; + } + + return 0; +} + +long cstring_find(const char self[], const char find[], size_t start_index) { + size_t sz = strlen(self); + if (sz > start_index) { + char *found = strstr(self + start_index, find); + if (found) { + return (long) (found - self); + } + } + + return -1; +} + +long cstring_rfind(char self[], const char find[], size_t rstart_index) { + size_t sz = strlen(self); + size_t sz_needle = strlen(find); + + if (rstart_index <= 0) + rstart_index += (sz - 1); + + if (sz > rstart_index && sz_needle <= sz) { + for (size_t i = sz - sz_needle; i; i--) { + if (cstring_sstarts_with(self, find, i)) + return i; + } + } + + return -1; +} + +int cstring_replace_car(cstring *self, char from, char to) { + size_t i; + int occur = 0; + + for (i = 0; i < self->length; i++) { + if (self->string[i] == from) { + self->string[i] = to; + occur++; + } + } + + return occur; +} + +int cstring_replace(cstring *self, const char from[], const char to[]) { + cstring *buffer; + size_t i; + size_t step; + char *swap; + int occur; + + // easy optimization: + if (from && to && from[0] && to[0] && !from[1] && !to[1]) + return cstring_replace_car(self, from[0], to[0]); + + // optimize for same-size strings? + + step = strlen(from) - 1; + buffer = new_cstring(); + occur = 0; + for (i = 0; i < self->length; i++) { + if (cstring_starts_with(self, from, i)) { + cstring_add(buffer, to); + i += step; + occur++; + } else { + cstring_add_car(buffer, self->string[i]); + } + } + + // not clean, but quicker: + swap = self->string; + self->string = buffer->string; + buffer->string = swap; + self->length = buffer->length; + + free_cstring(buffer); + return occur; +} + +void cstring_clear(cstring *self) { + self->length = 0; + self->string[0] = '\0'; +} + +void cstring_reverse(cstring *self) { + size_t i; + size_t last; + char tmp; + + if (self->length > 0) { + last = self->length - 1; + for (i = 0; i <= (last / 2); i++) { + tmp = self->string[i]; + self->string[i] = self->string[last - i]; + self->string[last - i] = tmp; + } + } +} + +void cstring_add_car(cstring *self, char source) { + char source2[2]; + + source2[0] = source; + source2[1] = '\0'; + + cstring_add(self, source2); +} + +void cstring_addp(cstring *self, const char *fmt, ...) { + va_list ap; + char empty = '\0'; + + va_start(ap, fmt); + int sz = vsnprintf(&empty, 0, fmt, ap); + va_end(ap); + + char *tmp = malloc((sz + 1) * sizeof(char)); + cstring_grow(self, sz); + + va_start(ap, fmt); + sz = vsnprintf(tmp, sz + 1, fmt, ap); + va_end(ap); + + cstring_add(self, tmp); + + free(tmp); +} + +void cstring_add(cstring *self, const char source[]) { + cstring_addf(self, source, 0); +} + +void cstring_addf(cstring *self, const char source[], size_t indexi) { + size_t ss, ptr; + + if (source != NULL && strlen(source) > indexi) { + ss = strlen(source) - indexi; + while ((self->length + ss) >= (self->priv->buffer_length)) { + self->priv->buffer_length += BUFFER_SIZE; + } + self->string = (char *) realloc(self->string, + sizeof(char) * self->priv->buffer_length); + + for (ptr = self->length; ptr <= (self->length + ss); ptr++) { + self->string[ptr] = source[ptr - self->length + indexi]; + } + self->length += ss; + } +} + +void cstring_addn(cstring *self, const char source[], size_t n) { + cstring_addfn(self, source, 0, n); +} + +void cstring_addfn(cstring *self, const char source[], size_t indexi, size_t n) { + size_t i; + char *tmp; + + for (i = indexi; i < (n + indexi) && source[i] != '\0'; i++) + ; + if (source[i] == '\0') { + cstring_addf(self, source, indexi); + } else { + tmp = (char *) malloc(sizeof(char) * (n + 1)); + strncpy(tmp, source + indexi, n); + tmp[n] = '\0'; + cstring_add(self, tmp); + free(tmp); + } +} + +void cstring_rtrim(cstring *self, char car) { + for (size_t i = self->length - 1; i >= 0; i--) { + if (self->string[i] != car) + break; + self->string[i] = '\0'; + self->length--; + } +} + +void cstring_trim(cstring *self, char car) { + if (car == '\0') + return; + + cstring_rtrim(self, car); + + int i = 0; + while (self->string[i] == car) + i++; + + if (i) { + cstring *tmp = new_cstring(); + cstring_add(tmp, self->string + i); + + free(self->string); + self->priv->buffer_length = tmp->priv->buffer_length; + self->string = tmp->string; + tmp->string = NULL; + free_cstring(tmp); + } +} + +size_t cstring_remove_crlf(cstring *self) { + size_t removed; + + removed = cstring_sremove_crlf(self->string, self->length); + self->length -= removed; + + return removed; +} + +size_t cstring_sremove_crlf(char data[], size_t n) { + size_t removed; + + removed = n; + while (removed > 0 + && (data[removed - 1] == '\r' || data[removed - 1] == '\n')) { + removed--; + } + + data[removed] = '\0'; + + return removed; +} + +void cstring_toupper(cstring *self) { + cstring_change_case(self, 1); +} + +void cstring_tolower(cstring *self) { + cstring_change_case(self, 0); +} + +void cstring_change_case(cstring *self, int up) { + wchar_t *wide; + char tmp[10]; + const char *src = self->string; + size_t s, i; + mbstate_t state; + +// init the state (passing NULL is not thread-safe) + memset(&state, '\0', sizeof(mbstate_t)); + +// won't contain MORE chars (but maybe less) + wide = (wchar_t *) malloc((self->length + 1) * sizeof(wchar_t)); + s = mbsrtowcs(wide, &src, self->length, &state); + wide[s] = (wchar_t) '\0'; + cstring_clear(self); + for (i = 0; i <= s; i++) { + if (up) + wide[i] = (wchar_t) towupper((wint_t) wide[i]); + else + wide[i] = (wchar_t) towlower((wint_t) wide[i]); + memset(&state, '\0', sizeof(mbstate_t)); + wcrtomb(tmp, wide[i], &state); + cstring_add(self, tmp); + } + free(wide); +} + +int cstring_readline(cstring *self, FILE *file) { + char buffer[BUFFER_SIZE]; + size_t size = 0; + int full_line; + +// sanity check: + if (!file) + return 0; + + buffer[BUFFER_SIZE - 1] = '\0'; // just in case + + if (!feof(file)) { + cstring_clear(self); + buffer[0] = '\0'; + + // Note: strlen() could return 0 if the file contains \0 + // at the start of a line + if (!fgets(buffer, (int) BUFFER_SIZE - 1, file)) + return 0; + size = strlen(buffer); + + full_line = ((file && feof(file)) || size == 0 + || buffer[size - 1] == '\n'); + size -= cstring_sremove_crlf(buffer, size); + cstring_add(self, buffer); + + // No luck, we need to continue getting data + while (!full_line) { + if (!fgets(buffer, (int) BUFFER_SIZE - 1, file)) + break; + size = strlen(buffer); + + full_line = ((file && feof(file)) || size == 0 + || buffer[size - 1] == '\n'); + size -= cstring_sremove_crlf(buffer, size); + cstring_add(self, buffer); + } + + return 1; + } + + return 0; +} + +void cstring_add_path(cstring *self, const char subpath[]) { + cstring_add_car(self, CSTRING_SEP); + cstring_add(self, subpath); +} + +int cstring_pop_path(cstring *self, int how_many) { + char sep[] = { CSTRING_SEP }; + int count = 0; + + cstring_rtrim(self, CSTRING_SEP); + for (int i = 0; i < how_many; i++) { + size_t idx = cstring_rfind(self->string, sep, 0); + if (!idx) + break; + + cstring_cut_at(self, idx - 1); + count++; + } + + return count; +} + +cstring *cstring_getdir(const char path[]) { + cstring *result; + size_t i; + + size_t sz = strlen(path); + + i = sz - 1; + if (i >= 0 && path[i] == CSTRING_SEP) + i--; + for (; i >= 0 && path[i] != CSTRING_SEP; i--) + ; + + if (i < 0) + return new_cstring(); + + result = new_cstring(); + cstring_addn(result, path, i); + + return result; +} + +cstring *cstring_getfile(cstring *path) { + cstring *result; + ssize_t i; + + i = (ssize_t) path->length - 1; + if (i >= 0 && path->string[i] == CSTRING_SEP) + i--; + for (; i >= 0 && path->string[i] != CSTRING_SEP; i--) + ; + + if (i < 0 || (size_t) (i + 1) >= path->length) + return new_cstring(); + + result = cstring_clones(path->string + i + 1); + return result; +} + +cstring *cstring_getfiles(const char path[]) { + cstring *copy, *result; + + copy = cstring_clones(path); + result = cstring_getfile(copy); + free_cstring(copy); + return result; +} + +cstring *cstring_to_b64(cstring *self) { + static base64 *cstring_b64 = NULL; + if (!cstring_b64) + cstring_b64 = base64_new(); + + return base64_encode(cstring_b64, self); +} + +char *cstring_to_sb64(cstring *self) { + static base64 *cstring_b64 = NULL; + if (!cstring_b64) + cstring_b64 = base64_new(); + + return cstring_convert(base64_encode(cstring_b64, self)); +} + +char *cstring_to_sb64s(char *self, size_t size) { + static base64 *cstring_b64 = NULL; + if (!cstring_b64) + cstring_b64 = base64_new(); + + return cstring_convert(base64_encodesi(cstring_b64, self, size)); +} + +cstring *cstring_from_b64(cstring *self) { + static base64 *cstring_b64 = NULL; + if (!cstring_b64) + cstring_b64 = base64_new(); + + return base64_decode(cstring_b64, self); +} + +char *cstring_from_sb64(cstring *self) { + static base64 *cstring_b64 = NULL; + if (!cstring_b64) + cstring_b64 = base64_new(); + + return cstring_convert(base64_decode(cstring_b64, self)); +} + +char *cstring_from_sb64s(char *self) { + static base64 *cstring_b64 = NULL; + if (!cstring_b64) + cstring_b64 = base64_new(); + + return cstring_convert(base64_decodes(cstring_b64, self)); +} + +int cstring_is_whole(cstring *self) { + return mbstowcs(NULL, self->string, 0) != (size_t) -1; +} diff --git a/src/utils/cstring.h b/src/utils/cstring.h new file mode 100644 index 0000000..60dc5b1 --- /dev/null +++ b/src/utils/cstring.h @@ -0,0 +1,535 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2013 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CSTRING_H +#define CSTRING_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * This is a cstring. It contains a suite of characters terminated by a NUL byte + * (or just a NUL byte), and a length. + * It is advised NOT to modify either of those directly. + * You can use cstring_convert to get a char*, though (in this case, the cstring + * MUST NOT be used again, and you are responsible for freeing the said char*). + */ +typedef struct cstring_struct cstring; +typedef struct cstring_private_struct cstring_private; + +struct cstring_struct { + char CNAME[32]; + char *string; + size_t length; + cstring_private *priv; +}; + +/** + * Instantiate a new cstring. + * + * Create (and allocate the memory for) a new cstring. + * Do not forget to call cstring_free(cstring) when done. + */ +cstring *new_cstring(); + +/** + * Free the given cstring. + * + * Free all the resources allocated for this cstring. + * + * @param string the cstring to free, which MUST NOT be used again afterward + */ +void free_cstring(cstring *string); + +//TODO: desc: will make sure it has enough space for MIN more chars +void cstring_grow(cstring *self, int min); + +//TODO: desc: will make sure it has enough space BUFFER chars (including NULL) +void cstring_grow_to(cstring *self, int buffer); + +/** + * Add a char at the end of the given cstring. + * + * @param self the cstring to work on + * @param source the character to add + */ +void cstring_add_car(cstring *self, char source); + +/** + * Add a string (a sequence of char that MUST end with '\0') at the end of the given cstring. + * + * @param self the cstring to work on + * @param source the string to add + */ +void cstring_add(cstring *self, const char source[]); + +/** + * Add a string (a sequence of char that MUST end with '\0') at the end of the given cstring, starting from index. + * + * @param self the cstring to work on + * @param source the string to add + * @param index the starting index at which to copy from the source + */ +void cstring_addf(cstring *self, const char source[], size_t index); + +/** + * Add a string (a sequence of char that MAY end with '\0') at the end of the given cstring, up to N chars long. + * + * @param self the cstring to work on + * @param source the string to add + * @param n the maximum number of chars to add (excluding the NUL byte) + */ +void cstring_addn(cstring *self, const char source[], size_t n); + +/** + * Add a string (a sequence of char that MAY end with '\0') at the end of the given cstring, starting from index, up to N chars long. + * + * @param self the cstring to work on + * @param source the string to add + * @param index the starting index at which to copy from the source + * @param n the maximum number of chars to add (excluding the NUL byte) + */ +void cstring_addfn(cstring *self, const char source[], size_t index, size_t n); + +//TODO: desc + tests +void cstring_addp(cstring *self, const char *fmt, ...); + +/** + * Cut the cstring at the given size if it is greater. + * E.g.: it will have (at most) this many characters (without counting NUL) in it after. + * + * @param self the cstring to work on + * @param size the size to cut at (the maximum size of the cstring after this operation, NUL excepted) + */ +void cstring_cut_at(cstring *self, size_t size); + +/** + * Create a substring of this cstring. + * + * @param self the cstring to work on + * @param start the index to start at + * @param length the number of characters to copy, 0 for 'up to the end' + * + * @return a newly allocated cstring + */ +cstring *cstring_substring(cstring *self, size_t start, size_t length); + +/** + * Split a cstring into "smaller" cstrings every time the given separator is found. + * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty). + * + * @param self the cstring to work on + * @param delim the separator, which can be longer than one character + * + * @return a list of cstring + */ +//TODO: use a [] +//clist *cstring_split(cstring *self, cstring *delim, cstring *quote); +/** + * Split a cstring into "smaller" cstrings every time the given separator (which MUST end in \0) is found. + * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty). + * + * @param self the cstring to work on + * @param delim the separator, which can be longer than one character and MUST end with \0 + * + * @return a list of cstring + */ +//TODO: use a [] +//clist *cstring_splits(cstring *self, const char delim[], const char quote[]); +/** + * Split a cstring into "smaller" cstrings every time the given separator is found. + * Will also allow empty fields, ie: "a,b,,c" will return 4 cstrings, the third being empty). + * + * @param self the cstring to work on + * @param delim the separator + * + * @return a list of cstring + */ +//TODO: use a [] +//clist *cstring_splitc(cstring *self, char delim, char quote); +/** + * Reverse the given cstring. + * + * @param self the cstring to work on + */ +void cstring_reverse(cstring *self); + +/** + * Replace all occurences of a string inside the given cstring by another. + * + * @param self the cstring to work on + * @param from the string to replace + * @param to the replacement string + * + * @return the number of occurences changed + */ +int cstring_replace(cstring *self, const char from[], const char to[]); + +/** + * Replace all occurences of a char inside the given cstring by another. + * + * @param self the cstring to work on + * @param from the char to replace + * @param to the replacement char + * + * @return the number of occurences changed + */ +int cstring_replace_car(cstring *self, char from, char to); + +/** + * Check if the cstring starts with the given pattern. + * + * @param self the cstring to work on + * @param find the string to find + * @param start_index the index at which to start the comparison + * + * @return 1 if it does + */ +int cstring_starts_with(cstring *self, const char find[], size_t start_index); + +/** + * Check if the string starts with the given pattern. + * + * @param self the string to work on + * @param find the string to find + * @param start_index the index at which to start the comparison + * + * @return 1 if it does + */ +int cstring_sstarts_with(const char string[], const char find[], + size_t start_index); + +/** + * Check if the cstring ends with the given pattern. + * + * @param self the cstring to work on + * @param find the string to find (if empty, will always be found) + * @param start_index the index at which to start the comparison + * + * @return 1 if it does + */ +int cstring_ends_with(cstring *self, const char find[]); + +/** + * Check if the string ends with the given pattern. + * + * @param self the string to work on + * @param find the string to find (if empty, will always be found) + * @param start_index the index at which to start the comparison + * + * @return 1 if it does + */ +int cstring_sends_with(const char self[], const char find[]); + +/** + * Find the given substring in this one. + * + * @param self the cstring to work on + * @param find the string to find + * @param start_index the index at which to start the search + * + * @return the start index of the found string if found, or a negative value + * if not + */ +long cstring_find(const char self[], const char find[], size_t rstart_index); + +/** + * Find the given substring in this one, but search in the reverse direction. + * + * @param self the cstring to work on + * @param find the string to find + * @param rstart_index the index at which to start the search, or 0 for + * "end of string" (remember that it is reverse, you would then never + * find anything with a real rstart_index of 0), or a negative value + * to count from the end of the string (-2 means 2 character before the + * end) + * + * @return the start index of the found string if found, or a negative value + * if not + */ +long cstring_rfind(char self[], const char find[], size_t rstart_index); + +/** + * Check if the given string is contained by this one. + * + * @param self the string to work on + * @param find the string to find + * @param start_index the index at which to start the comparison + * + * @return the start index of the found string if found, or a negative value if not + */ +long long cstring_sfind(const char self[], const char find[], + size_t start_index); + +/** + * Check if any of the given characters (in a char* which MUST end with '\0') is found. + * + * @param self the cstring to work on + * @param find the characters to find, which MUST be an array of char that ends with '\0' + * @param start_index the index at which to start the comparison + * + * @return the start index of the first found character if found, or a negative value if not + */ +long long cstring_find_any(cstring *self, const char find[], size_t start_index); + +/** + * Clear (truncate its size to 0) the given cstring. + * + * @param self the cstring to work on + */ +void cstring_clear(cstring *self); + +/** + * Convert this cstring into a string + * This means that you MUST NOT call cstring_free nor use the cstring anymore. + * NULL will return NULL. + * + * @param self the cstring to work on + */ +char *cstring_convert(cstring *self); + +/** + * Clone this cstring. + * NULL will return NULL. + * + * @param self the cstring to clone + */ +cstring *cstring_clone(cstring *self); + +/** + * Clone this string into a new cstring. + * NULL will return NULL. + * + * @param self the string to clone + */ +cstring *cstring_clones(const char self[]); + +/** + * Clone this cstring into a new string. + * NULL will return NULL. + * + * @param self the cstring to clone + */ +char *cstring_sclone(cstring *self); + +/** + * Clone this string into a new string. + * NULL will return NULL. + * + * @param self the string to clone + */ +char *cstring_sclones(const char self[]); + +/** + * Encode the string to BASE64. + * + * @param self the cstring to encode + */ +cstring *cstring_to_b64(cstring *self); + +/** + * Encode the string to BASE64. + * + * @param self the cstring to encode + */ +char *cstring_to_sb64(cstring *self); + +/** + * Encode the string to BASE64. + * + * @param self the string to encode + * @param size the size of the data (e.g., strlen(self)) + */ +char *cstring_to_sb64s(char *self, size_t size); + +/** + * Decode the string to BASE64. + * + * @param self the cstring to decode + */ +cstring *cstring_from_b64(cstring *self); + +/** + * Decode the string to BASE64. + * + * @param self the cstring to decode + */ +char *cstring_from_sb64(cstring *self); + +/** + * Decode the string to BASE64. + * + * @param self the string to decode + */ +char *cstring_from_sb64s(char *self); + +/** + * Trim this cstring of all trailing 'car' instances. + * + * @param self the cstring to work on + * @param car the character to trim (right only) + * + * @return a right trimmed cstring + */ +void cstring_rtrim(cstring *self, char car); + + +/** + * Trim this cstring of all 'car' instances from the start and/or the + * end of the string. + * + * @param self the cstring to work on + * @param car the character to trim + * + * @return a trimmed cstring + */ +void cstring_trim(cstring *self, char car); + +/** + * Compact the memory used by this cstring. + * + * @param self the cstring to work on + */ +void cstring_compact(cstring *self); + +/** + * Change the case to upper-case (UTF-8 compatible, but the string MUST be + * whole). + * + * @param self the cstring to work on + */ +void cstring_toupper(cstring *self); + +/** + * Change the case to lower-case (UTF-8 compatible, but the string MUST be + * whole). + * + * @param self the cstring to work on + */ +void cstring_tolower(cstring *self); + +/** + * Read a whole line (CR, LN or CR+LN terminated) from the given file stream. + * + * @param self the cstring to read into + * @param file the file to read + * + * @return 1 if a line was read, 0 if not + */ +int cstring_readline(cstring *self, FILE *file); + +/** + * Read a whole line (CR, LN or CR+LN terminated) from the given socket. + * + * @param self the cstring to read into + * @param fd the socket to read from + * + * @return 1 if a line was read, 0 if not + */ +int cstring_readnet(cstring *self, int fd); + +/** + * Add a path to the given cstring (if it is currently empty, it + * will result in a root path). + * + * Will be separated by a forward '/' except on non-standard systems + * that uses reverse slash (i.e., Windows). + * + * @param self the base cstring (empty for a root path) + * @param subpath the path component to add + */ +void cstring_add_path(cstring *self, const char subpath[]); + +//TODO: desc +int cstring_pop_path(cstring *self, int how_many); + +/** + * Return the basename component of this path (for instance, + * '/home/user/file.ext' becomes 'file.ext'). + * + * @param path the path to get the dir of (it can be a dir itself) + * + * @return a new string representing the parent directory + */ +char *cstring_basename(const char path[]); + +/** + * Return the dirname of this path (for instance, + * '/home/user/file.ext' becomes '/home/user'). + * + * @param path the path to get the dir of (it can be a dir itself) + * + * @return a new string representing the parent directory + */ +char *cstring_dirname(const char path[]); + +/** + * Return the latest path component of this path (usually a FILE). + * + * @param path the path to get the basename of (it can be a dir itself) + * + * @return a new cstring representing the latest path component + */ +cstring *cstring_getfile(cstring *path); + +/** + * Return the latest path component of this path (usually a FILE). + * + * @param path the path to get the basename of (it can be a dir itself) + * + * @return a new string representing the latest path component + */ +cstring *cstring_getfiles(const char path[]); + +/** + * Remove all the \r and \n at the end of the given cstring. + * + * @param self the cstring to change + * + * @return how many removed characters + */ +size_t cstring_remove_crlf(cstring *self); + +/** + * Remove all the \r and \n at the end of the given string. + * + * @param self the string to change + * @param n the size of the string + * + * @return how many removed characters + */ +size_t cstring_sremove_crlf(char *self, size_t n); + +/** + * Check if the string is whole (i.e., it doesn't contain incomplete UTF-8 + * sequences). + * + * @return TRUE if it is whole + */ +int cstring_is_whole(cstring *self); + +#endif + +#ifdef __cplusplus +} +#endif + diff --git a/src/utils/net.c b/src/utils/net.c new file mode 100644 index 0000000..f9fe50d --- /dev/null +++ b/src/utils/net.c @@ -0,0 +1,264 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2012 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#ifndef WIN32 + #include +#endif + +#include "net.h" +#include "cstring.h" + +#define bool int +#define true 1 +#define false 0 + +/** + * Get the sockaddr, IPv4 or IPv6. + * @param sa the socket address is in this structure + * @return the sockaddr_in or sockaddr_in6 inside this socketaddr, + * depending if it is IPv4 or IPv6 + */ +void *get_in_addr(struct sockaddr *sa); + +#ifndef WIN32 +/** + * Reap all zombie processes. + * This function will be called when a child process terminates, and + * will loop on all zombie processes to properly acknowledge them + * so they can die. + * + * @param s + */ +void sigchld_handler(int pid); + +void sigchld_handler(int pid) { + if (pid > 0 && pid < 0) pid = 0; + + // Reap all zombie processes + while (waitpid(-1, NULL, WNOHANG) > 0); +} +#endif + +int net_init(){ +#if defined (WIN32) + WSADATA WSAData; + return !WSAStartup(MAKEWORD(2,2), &WSAData); +#endif + return 1; +} + +void net_cleanup(){ +#if defined (WIN32) + WSACleanup(); +#endif +} + +int net_set_non_blocking(int fd) { + return net_set_blocking(fd, 0); +} + +int net_set_blocking(int fd, int block) { + int flags; + +/* If they have O_NONBLOCK, use the POSIX way to do it */ +#if defined (O_NONBLOCK) + /* O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */ + if ((flags = fcntl(fd, F_GETFL, 0)) == -1) { + flags = 0; + } + if (block) { + return fcntl(fd, F_SETFL, flags ^ O_NONBLOCK); + } + else { + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } +#else + flags = block?0:1; + return ioctl(fd, FIONBIO, (int)(&flags)); +#endif +} + +int net_connect(const char server[], int port) { + int sockfd; + struct addrinfo hints, *servinfo, *p; + int rv; + cstring *str; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + // convert the port number to a string + str = new_cstring(); + cstring_addp(str, "%i", port); + rv = getaddrinfo(server, str->string, &hints, &servinfo); + free_cstring(str); + // + + if (rv != 0) { + // DO NOT dirty the stderr + //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + // loop through all the results and connect to the first we can + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) + == -1) { + //perror("client: socket"); + continue; + } + + if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + //perror("client: connect"); + continue; + } + + break; + } + + if (p == NULL) { + //fprintf(stderr, "client: failed to connect\n"); + return -1; + } + + freeaddrinfo(servinfo); + + return sockfd; +} + +int net_accept(int ssocketd) { + struct sockaddr_storage their_addr; + socklen_t sin_size; + + sin_size = sizeof(their_addr); + return accept(ssocketd, (struct sockaddr *) &their_addr, &sin_size); +} + +int net_listen(int port, int backlog) { +#ifndef WIN32 + struct sigaction sa; +#endif + int sockfd; + struct addrinfo hints, *servinfo, *p; + char yes = 1; + int rv; + cstring *str; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; // use my IP + + // convert the port number to a string + str = new_cstring(); + cstring_addp(str, "%d", port); + rv = getaddrinfo(NULL, str->string, &hints, &servinfo); + free_cstring(str); + // + + if (rv != 0) { + // DO NOT dirty the stderr + //fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return -1; + } + + // loop through all the results and bind to the first we can + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { + //perror("server: socket"); + continue; + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { + //perror("setsockopt"); + return -1; + } + + if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + //perror("server: bind"); + continue; + } + + break; + } + + if (p == NULL) { + //fprintf(stderr, "server: failed to bind\n"); + return -1; + } + + // all done with this structure + freeaddrinfo(servinfo); + + if (listen(sockfd, backlog) == -1) { + //perror("listen"); + return -1; + } + +#ifndef WIN32 + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + //perror("sigaction"); + return -1; + } +#endif + + return sockfd; +} + +void net_close_socketd(int socketd) { + close(socketd); +} + +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif +ssize_t net_write(int fd, const void *buf, size_t n) { + // In UNIX: send() with flag set to '0' == write() + // In WIN32: cannot write() to a socket + return send(fd, (char *)buf, n, MSG_NOSIGNAL); +} + +ssize_t net_read(int fd, void *buf, size_t nbytes) { + // In UNIX: recv() with flag set to '0' == send() + // In WIN32: cannot read() from a socket + return recv(fd, (char *)buf, nbytes, 0); +} + +void *get_in_addr(struct sockaddr *sa) { + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*) sa)->sin_addr); + } + else { + return &(((struct sockaddr_in6*) sa)->sin6_addr); + } +} + diff --git a/src/utils/net.h b/src/utils/net.h new file mode 100644 index 0000000..9652a4d --- /dev/null +++ b/src/utils/net.h @@ -0,0 +1,168 @@ +/* + * CUtils: some small C utilities + * + * Copyright (C) 2012 Niki Roo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * @file + * @author niki + * @date November 2011 + * + * Allows you to make connections to/from a server, and to send/receive data. + */ + +#ifndef NET_H +#define NET_H + +#ifdef __cplusplus +extern "C" { +#endif + +// WHY ?? +#ifdef _WIN32 + #ifndef WIN32 + #define WIN32 + #endif +#endif + +#ifdef WIN32 + #ifndef _WIN32_WINNT + #define _WIN32_WINNT 0x501 + #endif + #include + #include + typedef SSIZE_T ssize_t; + #define close(s) closesocket(s) + #define ioctl(a,b,c) ioctlsocket(a,b,c) + #pragma comment(lib, "wsock32.lib"); + #pragma comment(lib, "ws2_32.lib"); +#else + #include + #include + #include + #include + #include + #include +#endif + +/* for ssize_t */ +#include + +/** + * Obsolete, see net_set_blocking(..) + */ +int net_set_non_blocking(int fd); + +/** + * You must call this function before doing any network related operation, + * because of some legacy WIN32 rule. + * (It is a noop on all other platforms). + * + * @return 0 when the Windows host does not support WinSock and thus, + * no network for you (you still need to call net_cleanup) + * + */ +int net_init(); + +/** + * You must call this function after you are done using network related + * operations within this DLL, because of some legacy WIN32 rule. + * (It is a noop on all other platforms). + */ +void net_cleanup(); + +/** + * Set the given socket to (non-)blocking I/O mode. + * This function can work with file sockets, too. + * + * @param fd the file descriptor or socket to change + * @param block 1 to block, 0 not to block + * + * @return 1 if success + */ +int net_set_blocking(int fd, int block); + +/** + * Connect to this server on this port, and return a socket descriptor + * to write to or read from it. + * + * @param server the server to connect to + * @param port the port to connect on + * + * @return the server socket or -1 if error + */ +int net_connect(const char server[], int port); + +/** + * Open a port and returns a (server) socket descriptor from which you can + * accept connection. + * + * @param port the port to connect on + * @param backlog the maximum number of client connections we will queue for + * this socket until you handle them + * + * @return the server socket, or a negative value on error + */ +int net_listen(int port, int backlog); + +/** + * Block (or not) and wait for a client to connect on this socket. + * When this is done, return a socket to this specific client/server + * connection. It can takes the connections from a queue, + * as defined in net_listen. + * + * @param ssocketd the server socket on which to accept a connection + * + * @return the socket, or a negative value on error + */ +int net_accept(int ssocketd); + +/** + * Close a socket (or a server socket). + * + * @param socketd the (server) socket to close + */ +void net_close_socketd(int socketd); + +/** + * Write to this socket, as you would with a file. + * + * @param fd the socket to write to + * @param buf the buffer to read from + * @param n the number of bytes to write + * + * @return the actual number of bytes written or a + * negative number if error + */ +ssize_t net_write(int fd, const void *buf, size_t n); + +/** + * Read from this socket, as you would with a file. + * + * @param fd the socket to read from + * @param buf the buffer to write to + * @param nbytes the number of bytes to read + * + * @return the actual number of bytes read + */ +ssize_t net_read(int fd, void *buf, size_t nbytes); + +#endif + +#ifdef __cplusplus +extern } +#endif