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