1 /* misc_tools.c
2 *
3 *
4 * Copyright (C) 2014 Toxic All Rights Reserved.
5 *
6 * This file is part of Toxic.
7 *
8 * Toxic is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * Toxic is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with Toxic. If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22
23 #include <arpa/inet.h>
24 #include <ctype.h>
25 #include <limits.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <time.h>
30
31 #include "file_transfers.h"
32 #include "misc_tools.h"
33 #include "settings.h"
34 #include "toxic.h"
35 #include "windows.h"
36
37 extern ToxWindow *prompt;
38 extern struct user_settings *user_settings;
39
clear_screen(void)40 void clear_screen(void)
41 {
42 printf("\033[2J\033[1;1H");
43 }
44
hst_to_net(uint8_t * num,uint16_t numbytes)45 void hst_to_net(uint8_t *num, uint16_t numbytes)
46 {
47 #ifndef WORDS_BIGENDIAN
48 uint8_t *buff = malloc(numbytes);
49
50 if (buff == NULL) {
51 return;
52 }
53
54 for (uint32_t i = 0; i < numbytes; ++i) {
55 buff[i] = num[numbytes - i - 1];
56 }
57
58 memcpy(num, buff, numbytes);
59 free(buff);
60 #endif
61 }
62
get_unix_time(void)63 time_t get_unix_time(void)
64 {
65 return time(NULL);
66 }
67
68 /* Returns 1 if connection has timed out, 0 otherwise */
timed_out(time_t timestamp,time_t timeout)69 int timed_out(time_t timestamp, time_t timeout)
70 {
71 return timestamp + timeout <= get_unix_time();
72 }
73
74 /* Attempts to sleep the caller's thread for `usec` microseconds */
sleep_thread(long int usec)75 void sleep_thread(long int usec)
76 {
77 struct timespec req;
78 struct timespec rem;
79
80 req.tv_sec = 0;
81 req.tv_nsec = usec * 1000L;
82
83 if (nanosleep(&req, &rem) == -1) {
84 if (nanosleep(&rem, NULL) == -1) {
85 fprintf(stderr, "nanosleep() returned -1\n");
86 }
87 }
88 }
89
90 /* Get the current local time */
get_time(void)91 struct tm *get_time(void)
92 {
93 struct tm *timeinfo;
94 time_t t = get_unix_time();
95 timeinfo = localtime((const time_t *) &t);
96 return timeinfo;
97 }
98
99 /* Puts the current time in buf in the format of specified by the config */
get_time_str(char * buf,size_t bufsize)100 void get_time_str(char *buf, size_t bufsize)
101 {
102 if (buf == NULL || bufsize == 0) {
103 return;
104 }
105
106 *buf = 0;
107
108 if (user_settings->timestamps == TIMESTAMPS_OFF) {
109 return;
110 }
111
112 const char *t = user_settings->timestamp_format;
113
114 if (strftime(buf, bufsize, t, get_time()) == 0) {
115 strftime(buf, bufsize, TIMESTAMP_DEFAULT, get_time());
116 }
117 }
118
119 /* Converts seconds to string in format HH:mm:ss; truncates hours and minutes when necessary */
get_elapsed_time_str(char * buf,int bufsize,time_t secs)120 void get_elapsed_time_str(char *buf, int bufsize, time_t secs)
121 {
122 if (!secs) {
123 return;
124 }
125
126 long int seconds = secs % 60;
127 long int minutes = (secs % 3600) / 60;
128 long int hours = secs / 3600;
129
130 if (!minutes && !hours) {
131 snprintf(buf, bufsize, "%.2ld", seconds);
132 } else if (!hours) {
133 snprintf(buf, bufsize, "%ld:%.2ld", minutes, seconds);
134 } else {
135 snprintf(buf, bufsize, "%ld:%.2ld:%.2ld", hours, minutes, seconds);
136 }
137 }
138
139 /*
140 * Converts a hexidecimal string representation of a Tox public key to binary format and puts
141 * the result in output.
142 *
143 * `hex_len` must be exactly TOX_PUBLIC_KEY_SIZE * 2, and `output_size` must have room
144 * for TOX_PUBLIC_KEY_SIZE bytes.
145 *
146 * Returns 0 on success.
147 * Returns -1 on failure.
148 */
tox_pk_string_to_bytes(const char * hex_string,size_t hex_len,char * output,size_t output_size)149 int tox_pk_string_to_bytes(const char *hex_string, size_t hex_len, char *output, size_t output_size)
150 {
151 if (output_size != TOX_PUBLIC_KEY_SIZE || hex_len != output_size * 2) {
152 return -1;
153 }
154
155 for (size_t i = 0; i < output_size; ++i) {
156 sscanf(hex_string, "%2hhx", (unsigned char *)&output[i]);
157 hex_string += 2;
158 }
159
160 return 0;
161 }
162
163 /* Convert a hexadecimcal string of length `size` to bytes and puts the result in `keystr`.
164 *
165 * Returns 0 on success.
166 * Returns -1 on failure.
167 */
hex_string_to_bytes(char * buf,int size,const char * keystr)168 int hex_string_to_bytes(char *buf, int size, const char *keystr)
169 {
170 if (size % 2 != 0) {
171 return -1;
172 }
173
174 const char *pos = keystr;
175
176 for (size_t i = 0; i < size; ++i) {
177 int res = sscanf(pos, "%2hhx", (unsigned char *)&buf[i]);
178 pos += 2;
179
180 if (res == EOF || res < 1) {
181 return -1;
182 }
183 }
184
185 return 0;
186 }
187
188 /* Converts a binary representation of a Tox ID into a string.
189 *
190 * `bin_id_size` must be exactly TOX_ADDRESS_SIZE bytes in length, and
191 * `output_size` must be at least TOX_ADDRESS_SIZE * 2 + 1.
192 *
193 * Returns 0 on success.
194 * Returns -1 on failure.
195 */
tox_id_bytes_to_str(const char * bin_id,size_t bin_id_size,char * output,size_t output_size)196 int tox_id_bytes_to_str(const char *bin_id, size_t bin_id_size, char *output, size_t output_size)
197 {
198 if (bin_id_size != TOX_ADDRESS_SIZE || output_size < (TOX_ADDRESS_SIZE * 2 + 1)) {
199 return -1;
200 }
201
202 for (size_t i = 0; i < TOX_ADDRESS_SIZE; ++i) {
203 snprintf(&output[i * 2], output_size - (i * 2), "%02X", bin_id[i] & 0xff);
204 }
205
206 return 0;
207 }
208
209 /* Converts a binary representation of a Tox public key into a string.
210 *
211 * `bin_pubkey_size` must be exactly TOX_PUBLIC_KEY_SIZE bytes in size, and
212 * `output_size` must be at least TOX_PUBLIC_KEY_SIZE * 2 + 1.
213 *
214 * Returns 0 on success.
215 * Returns -1 on failure.
216 */
tox_pk_bytes_to_str(const uint8_t * bin_pubkey,size_t bin_pubkey_size,char * output,size_t output_size)217 int tox_pk_bytes_to_str(const uint8_t *bin_pubkey, size_t bin_pubkey_size, char *output, size_t output_size)
218 {
219 if (bin_pubkey_size != TOX_PUBLIC_KEY_SIZE || output_size < (TOX_PUBLIC_KEY_SIZE * 2 + 1)) {
220 return -1;
221 }
222
223 for (size_t i = 0; i < TOX_PUBLIC_KEY_SIZE; ++i) {
224 snprintf(&output[i * 2], output_size - (i * 2), "%02X", bin_pubkey[i] & 0xff);
225 }
226
227 return 0;
228 }
229
230 /* Returns 1 if the string is empty, 0 otherwise */
string_is_empty(const char * string)231 int string_is_empty(const char *string)
232 {
233 if (!string) {
234 return true;
235 }
236
237 return string[0] == '\0';
238 }
239
240 /* Returns 1 if the string is empty, 0 otherwise */
wstring_is_empty(const wchar_t * string)241 int wstring_is_empty(const wchar_t *string)
242 {
243 if (!string) {
244 return true;
245 }
246
247 return string[0] == L'\0';
248 }
249
250 /* convert a multibyte string to a wide character string and puts in buf. */
mbs_to_wcs_buf(wchar_t * buf,const char * string,size_t n)251 int mbs_to_wcs_buf(wchar_t *buf, const char *string, size_t n)
252 {
253 size_t len = mbstowcs(NULL, string, 0) + 1;
254
255 if (n < len) {
256 return -1;
257 }
258
259 if ((len = mbstowcs(buf, string, n)) == (size_t) - 1) {
260 return -1;
261 }
262
263 return len;
264 }
265
266 /* converts wide character string into a multibyte string and puts in buf. */
wcs_to_mbs_buf(char * buf,const wchar_t * string,size_t n)267 int wcs_to_mbs_buf(char *buf, const wchar_t *string, size_t n)
268 {
269 size_t len = wcstombs(NULL, string, 0) + 1;
270
271 if (n < len) {
272 return -1;
273 }
274
275 if ((len = wcstombs(buf, string, n)) == (size_t) - 1) {
276 return -1;
277 }
278
279 return len;
280 }
281
282 /* case-insensitive string compare function for use with qsort */
qsort_strcasecmp_hlpr(const void * str1,const void * str2)283 int qsort_strcasecmp_hlpr(const void *str1, const void *str2)
284 {
285 return strcasecmp((const char *) str1, (const char *) str2);
286 }
287
288 /* case-insensitive string compare function for use with qsort */
qsort_ptr_char_array_helper(const void * str1,const void * str2)289 int qsort_ptr_char_array_helper(const void *str1, const void *str2)
290 {
291 return strcasecmp(*(const char *const *)str1, *(const char *const *)str2);
292 }
293
294 static const char invalid_chars[] = {'/', '\n', '\t', '\v', '\r', '\0'};
295
296 /*
297 * Helper function for `valid_nick()`.
298 *
299 * Returns true if `ch` is not in the `invalid_chars` array.
300 */
is_valid_char(char ch)301 static bool is_valid_char(char ch)
302 {
303 char tmp;
304
305 for (size_t i = 0; (tmp = invalid_chars[i]); ++i) {
306 if (tmp == ch) {
307 return false;
308 }
309 }
310
311 return true;
312 }
313
314 /* Returns true if nick is valid.
315 *
316 * A valid toxic nick:
317 * - cannot be empty
318 * - cannot start with a space
319 * - must not contain a forward slash (for logfile naming purposes)
320 * - must not contain contiguous spaces
321 * - must not contain a newline or tab seqeunce
322 */
valid_nick(const char * nick)323 bool valid_nick(const char *nick)
324 {
325 if (!nick[0] || nick[0] == ' ') {
326 return false;
327 }
328
329 for (size_t i = 0; nick[i]; ++i) {
330 char ch = nick[i];
331
332 if ((ch == ' ' && nick[i + 1] == ' ') || !is_valid_char(ch)) {
333 return false;
334 }
335 }
336
337 return true;
338 }
339
340 /* Converts all newline/tab chars to spaces (use for strings that should be contained to a single line) */
filter_str(char * str,size_t len)341 void filter_str(char *str, size_t len)
342 {
343 for (size_t i = 0; i < len; ++i) {
344 char ch = str[i];
345
346 if (!is_valid_char(ch) || str[i] == '\0') {
347 str[i] = ' ';
348 }
349 }
350 }
351
352 /* gets base file name from path or original file name if no path is supplied.
353 * Returns the file name length
354 */
get_file_name(char * namebuf,size_t bufsize,const char * pathname)355 size_t get_file_name(char *namebuf, size_t bufsize, const char *pathname)
356 {
357 int len = strlen(pathname) - 1;
358 char *path = strdup(pathname);
359
360 if (path == NULL) {
361 exit_toxic_err("failed in get_file_name", FATALERR_MEMORY);
362 }
363
364 while (len >= 0 && pathname[len] == '/') {
365 path[len--] = '\0';
366 }
367
368 char *finalname = strdup(path);
369
370 if (finalname == NULL) {
371 exit_toxic_err("failed in get_file_name", FATALERR_MEMORY);
372 }
373
374 const char *basenm = strrchr(path, '/');
375
376 if (basenm != NULL) {
377 if (basenm[1]) {
378 strcpy(finalname, &basenm[1]);
379 }
380 }
381
382 snprintf(namebuf, bufsize, "%s", finalname);
383 free(finalname);
384 free(path);
385
386 return strlen(namebuf);
387 }
388
389 /* Gets the base directory of path and puts it in dir.
390 * dir must have at least as much space as path_len + 1.
391 *
392 * Returns the length of the base directory.
393 */
get_base_dir(const char * path,size_t path_len,char * dir)394 size_t get_base_dir(const char *path, size_t path_len, char *dir)
395 {
396 if (path_len == 0 || path == NULL) {
397 return 0;
398 }
399
400 size_t dir_len = char_rfind(path, '/', path_len);
401
402 if (dir_len != 0 && dir_len < path_len) {
403 ++dir_len; /* Leave trailing slash */
404 }
405
406 memcpy(dir, path, dir_len);
407 dir[dir_len] = '\0';
408
409 return dir_len;
410 }
411
412 /* converts str to all lowercase */
str_to_lower(char * str)413 void str_to_lower(char *str)
414 {
415 int i;
416
417 for (i = 0; str[i]; ++i) {
418 str[i] = tolower(str[i]);
419 }
420 }
421
422 /* puts friendnum's nick in buf, truncating at TOXIC_MAX_NAME_LENGTH if necessary.
423 if toxcore API call fails, put UNKNOWN_NAME in buf
424 Returns nick len */
get_nick_truncate(Tox * m,char * buf,uint32_t friendnum)425 size_t get_nick_truncate(Tox *m, char *buf, uint32_t friendnum)
426 {
427 Tox_Err_Friend_Query err;
428 size_t len = tox_friend_get_name_size(m, friendnum, &err);
429
430 if (err != TOX_ERR_FRIEND_QUERY_OK) {
431 goto on_error;
432 } else {
433 if (!tox_friend_get_name(m, friendnum, (uint8_t *) buf, NULL)) {
434 goto on_error;
435 }
436 }
437
438 len = MIN(len, TOXIC_MAX_NAME_LENGTH - 1);
439 buf[len] = '\0';
440 filter_str(buf, len);
441 return len;
442
443 on_error:
444 strcpy(buf, UNKNOWN_NAME);
445 len = strlen(UNKNOWN_NAME);
446 buf[len] = '\0';
447 return len;
448 }
449
450 /* same as get_nick_truncate but for conferences */
get_conference_nick_truncate(Tox * m,char * buf,uint32_t peernum,uint32_t conferencenum)451 int get_conference_nick_truncate(Tox *m, char *buf, uint32_t peernum, uint32_t conferencenum)
452 {
453 Tox_Err_Conference_Peer_Query err;
454 size_t len = tox_conference_peer_get_name_size(m, conferencenum, peernum, &err);
455
456 if (err != TOX_ERR_CONFERENCE_PEER_QUERY_OK) {
457 goto on_error;
458 } else {
459 if (!tox_conference_peer_get_name(m, conferencenum, peernum, (uint8_t *) buf, NULL)) {
460 goto on_error;
461 }
462 }
463
464 len = MIN(len, TOXIC_MAX_NAME_LENGTH - 1);
465 buf[len] = '\0';
466 filter_str(buf, len);
467 return len;
468
469 on_error:
470 strcpy(buf, UNKNOWN_NAME);
471 len = strlen(UNKNOWN_NAME);
472 buf[len] = '\0';
473 return len;
474 }
475
476 /* copies data to msg buffer, removing return characters.
477 returns length of msg, which will be no larger than size-1 */
copy_tox_str(char * msg,size_t size,const char * data,size_t length)478 size_t copy_tox_str(char *msg, size_t size, const char *data, size_t length)
479 {
480 size_t i;
481 size_t j = 0;
482
483 for (i = 0; (i < length) && (j < size - 1); ++i) {
484 if (data[i] != '\r') {
485 msg[j++] = data[i];
486 }
487 }
488
489 msg[j] = '\0';
490
491 return j;
492 }
493
494 /* returns index of the first instance of ch in s starting at idx.
495 returns length of s if char not found or 0 if s is NULL. */
char_find(int idx,const char * s,char ch)496 int char_find(int idx, const char *s, char ch)
497 {
498 if (!s) {
499 return 0;
500 }
501
502 int i = idx;
503
504 for (i = idx; s[i]; ++i) {
505 if (s[i] == ch) {
506 break;
507 }
508 }
509
510 return i;
511 }
512
513 /* returns index of the last instance of ch in s starting at len.
514 returns 0 if char not found or s is NULL (skips 0th index). */
char_rfind(const char * s,char ch,int len)515 int char_rfind(const char *s, char ch, int len)
516 {
517 if (!s) {
518 return 0;
519 }
520
521 int i = 0;
522
523 for (i = len; i > 0; --i) {
524 if (s[i] == ch) {
525 break;
526 }
527 }
528
529 return i;
530 }
531
532 /* Converts bytes to appropriate unit and puts in buf as a string */
bytes_convert_str(char * buf,int size,uint64_t bytes)533 void bytes_convert_str(char *buf, int size, uint64_t bytes)
534 {
535 double conv = bytes;
536 const char *unit;
537
538 if (conv < KiB) {
539 unit = "Bytes";
540 } else if (conv < MiB) {
541 unit = "KiB";
542 conv /= (double) KiB;
543 } else if (conv < GiB) {
544 unit = "MiB";
545 conv /= (double) MiB;
546 } else {
547 unit = "GiB";
548 conv /= (double) GiB;
549 }
550
551 snprintf(buf, size, "%.1f %s", conv, unit);
552 }
553
554 /* checks if a file exists. Returns true or false */
file_exists(const char * path)555 bool file_exists(const char *path)
556 {
557 struct stat s;
558 return stat(path, &s) == 0;
559 }
560
561 /*
562 * Checks the file type path points to and returns a File_Type enum value.
563 *
564 * Returns FILE_TYPE_DIRECTORY if path points to a directory.
565 * Returns FILE_TYPE_REGULAR if path points to a regular file.
566 * Returns FILE_TYPE_OTHER on any other result, including an invalid path.
567 */
file_type(const char * path)568 File_Type file_type(const char *path)
569 {
570 struct stat s;
571
572 if (stat(path, &s) == -1) {
573 return FILE_TYPE_OTHER;
574 }
575
576 switch (s.st_mode & S_IFMT) {
577 case S_IFDIR:
578 return FILE_TYPE_DIRECTORY;
579
580 case S_IFREG:
581 return FILE_TYPE_REGULAR;
582
583 default:
584 return FILE_TYPE_OTHER;
585 }
586 }
587
588 /* returns file size. If file doesn't exist returns 0. */
file_size(const char * path)589 off_t file_size(const char *path)
590 {
591 struct stat st;
592
593 if (stat(path, &st) == -1) {
594 return 0;
595 }
596
597 return st.st_size;
598 }
599
600 /* sets window title in tab bar. */
set_window_title(ToxWindow * self,const char * title,int len)601 void set_window_title(ToxWindow *self, const char *title, int len)
602 {
603 if (len <= 0 || !title) {
604 return;
605 }
606
607 char cpy[TOXIC_MAX_NAME_LENGTH + 1];
608
609 if (self->type == WINDOW_TYPE_CONFERENCE) { /* keep conferencenumber in title for invites */
610 snprintf(cpy, sizeof(cpy), "%u %s", self->num, title);
611 } else {
612 snprintf(cpy, sizeof(cpy), "%s", title);
613 }
614
615 if (len > MAX_WINDOW_NAME_LENGTH) {
616 strcpy(&cpy[MAX_WINDOW_NAME_LENGTH - 3], "...");
617 cpy[MAX_WINDOW_NAME_LENGTH] = '\0';
618 }
619
620 snprintf(self->name, sizeof(self->name), "%s", cpy);
621 }
622
623 /*
624 * Frees all members of a pointer array plus `arr`.
625 */
free_ptr_array(void ** arr)626 void free_ptr_array(void **arr)
627 {
628 if (arr == NULL) {
629 return;
630 }
631
632 void **tmp = arr;
633
634 while (*arr) {
635 free(*arr);
636 ++arr;
637 }
638
639 free(tmp);
640 }
641
642 /*
643 * Returns a null terminated array of `length` pointers. Each pointer is allocated `bytes` bytes.
644 * Returns NULL on failure.
645 *
646 * The caller is responsible for freeing the array with `free_ptr_array`.
647 */
malloc_ptr_array(size_t length,size_t bytes)648 void **malloc_ptr_array(size_t length, size_t bytes)
649 {
650 void **arr = malloc((length + 1) * sizeof(void *));
651
652 if (arr == NULL) {
653 return NULL;
654 }
655
656 for (size_t i = 0; i < length; ++i) {
657 arr[i] = malloc(bytes);
658
659 if (arr[i] == NULL) {
660 free_ptr_array(arr);
661 return NULL;
662 }
663 }
664
665 arr[length] = NULL;
666
667 return arr;
668 }
669