1 /*
2  * Copyright 2015 Artem Savkov <artem.savkov@gmail.com>
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 #include "discord-util.h"
18 #include <http_client.h>
19 #include <stdarg.h>
20 #include <inttypes.h>
21 
discord_debug(char * format,...)22 void discord_debug(char *format, ...)
23 {
24   gchar *buf;
25   va_list params;
26   va_start(params, format);
27   buf = g_strdup_vprintf(format, params);
28   va_end(params);
29 
30   if (getenv("BITLBEE_DEBUG")) {
31     GDateTime *dt = g_date_time_new_now_local();
32     gchar *tstr = g_date_time_format(dt, "%T");
33 
34     g_print("[%s] %s\n", tstr, buf);
35 
36     g_free(tstr);
37     g_date_time_unref(dt);
38   }
39   g_free(buf);
40 }
41 
free_user_info(user_info * uinfo)42 void free_user_info(user_info *uinfo)
43 {
44   g_free(uinfo->name);
45   g_free(uinfo->id);
46   g_free(uinfo);
47 }
48 
free_channel_info(channel_info * cinfo)49 void free_channel_info(channel_info *cinfo)
50 {
51   g_free(cinfo->id);
52   cinfo->id = NULL;
53 
54   g_slist_free_full(cinfo->pinned, (GDestroyNotify)g_free);
55   switch (cinfo->type) {
56     case CHANNEL_TEXT:
57       if (cinfo->to.channel.gc != NULL) {
58         imcb_chat_free(cinfo->to.channel.gc);
59       }
60       g_free(cinfo->to.channel.name);
61       g_free(cinfo->to.channel.bci->title);
62       g_free(cinfo->to.channel.bci->topic);
63       g_free(cinfo->to.channel.bci);
64       break;
65     case CHANNEL_GROUP_PRIVATE:
66       if (cinfo->to.group.gc != NULL) {
67         imcb_chat_free(cinfo->to.group.gc);
68       }
69       g_free(cinfo->to.group.name);
70       g_free(cinfo->to.group.bci->title);
71       g_free(cinfo->to.group.bci->topic);
72       g_free(cinfo->to.group.bci);
73       g_slist_free(cinfo->to.group.users);
74       break;
75     default:
76       g_free(cinfo->to.handle.name);
77       break;
78   }
79 
80   g_free(cinfo);
81 }
82 
free_server_info(server_info * sinfo)83 void free_server_info(server_info *sinfo)
84 {
85   g_free(sinfo->name);
86   g_free(sinfo->id);
87 
88   g_slist_free_full(sinfo->channels, (GDestroyNotify)free_channel_info);
89   g_slist_free_full(sinfo->users, (GDestroyNotify)free_user_info);
90 
91   g_free(sinfo);
92 }
93 
free_gw_data(gw_data * gw)94 void free_gw_data(gw_data *gw)
95 {
96   if (gw != NULL) {
97     g_free(gw->addr);
98     g_free(gw->path);
99 
100     g_free(gw);
101   }
102 }
103 
free_pending_req(struct http_request * req)104 static void free_pending_req(struct http_request *req)
105 {
106   http_close(req);
107 }
108 
free_pending_ev(gpointer * ev)109 static void free_pending_ev(gpointer *ev)
110 {
111   b_event_remove(GPOINTER_TO_INT(ev));
112 }
113 
free_discord_data(discord_data * dd)114 void free_discord_data(discord_data *dd)
115 {
116   g_hash_table_destroy(dd->sent_message_ids);
117   g_slist_free_full(dd->pending_events, (GDestroyNotify)free_pending_ev);
118   g_slist_free_full(dd->pending_reqs, (GDestroyNotify)free_pending_req);
119   g_slist_free_full(dd->pchannels, (GDestroyNotify)free_channel_info);
120   g_slist_free_full(dd->servers, (GDestroyNotify)free_server_info);
121 
122   free_gw_data(dd->gateway);
123   g_free(dd->token);
124   g_free(dd->uname);
125   g_free(dd->session_id);
126   g_free(dd->id);
127 
128   g_free(dd);
129 }
130 
cmp_chan_id(const channel_info * cinfo,const char * chan_id)131 static gint cmp_chan_id(const channel_info *cinfo, const char *chan_id)
132 {
133   return g_strcmp0(cinfo->id, chan_id);
134 }
135 
cmp_chan_name(const channel_info * cinfo,const char * cname)136 static gint cmp_chan_name(const channel_info *cinfo, const char *cname)
137 {
138   gchar *ciname = NULL;
139   if (cinfo->type == CHANNEL_TEXT) {
140     ciname = cinfo->to.channel.name;
141   } else if (cinfo->type == CHANNEL_GROUP_PRIVATE) {
142     ciname = cinfo->to.group.name;
143   } else {
144     ciname = cinfo->to.handle.name;
145   }
146 
147   return g_strcmp0(ciname, cname);
148 }
149 
cmp_chan_fname(const channel_info * cinfo,const char * cname)150 static gint cmp_chan_fname(const channel_info *cinfo, const char *cname)
151 {
152   gchar *ciname = NULL;
153   if (cinfo->type == CHANNEL_TEXT) {
154     ciname = cinfo->to.channel.bci->title;
155   } else if (cinfo->type == CHANNEL_GROUP_PRIVATE) {
156     ciname = cinfo->to.group.bci->title;
157   }
158 
159   return g_strcmp0(ciname, cname);
160 }
161 
cmp_chan_name_ignorecase(const channel_info * cinfo,const char * cname)162 static gint cmp_chan_name_ignorecase(const channel_info *cinfo,
163                                      const char *cname)
164 {
165   gchar *cfn1 = NULL;
166   if (cinfo->type == CHANNEL_TEXT) {
167     cfn1 = g_utf8_casefold(cinfo->to.channel.name, -1);
168   } else if (cinfo->type == CHANNEL_GROUP_PRIVATE) {
169     cfn1 = g_utf8_casefold(cinfo->to.group.name, -1);
170   } else {
171     cfn1 = g_utf8_casefold(cinfo->to.handle.name, -1);
172   }
173 
174   gchar *cfn2 = g_utf8_casefold(cname, -1);
175   gint result = g_strcmp0(cfn1, cfn2);
176 
177   g_free(cfn1);
178   g_free(cfn2);
179   return result;
180 }
181 
cmp_user_id(const user_info * uinfo,const char * user_id)182 static gint cmp_user_id(const user_info *uinfo, const char *user_id)
183 {
184   return g_strcmp0(uinfo->id, user_id);
185 }
186 
cmp_user_name(const user_info * uinfo,const char * uname)187 static gint cmp_user_name(const user_info *uinfo, const char *uname)
188 {
189   return g_strcmp0(uinfo->name, uname);
190 }
191 
cmp_user_name_ignorecase(const user_info * uinfo,const char * uname)192 static gint cmp_user_name_ignorecase(const user_info *uinfo, const char *uname)
193 {
194   gchar *cfn1 = g_utf8_casefold(uinfo->name, -1);
195   gchar *cfn2 = g_utf8_casefold(uname, -1);
196   gint result = g_strcmp0(cfn1, cfn2);
197 
198   g_free(cfn1);
199   g_free(cfn2);
200   return result;
201 }
202 
cmp_irc_user_name(const user_info * uinfo,const char * uname)203 static gint cmp_irc_user_name(const user_info *uinfo, const char *uname)
204 {
205   gint result = -1;
206   irc_user_t *iu = (irc_user_t*)uinfo->user->ui_data;
207 
208   if (iu != NULL) {
209     result = g_strcmp0(iu->nick, uname);
210   }
211   return result;
212 }
213 
cmp_irc_user_name_ignorecase(const user_info * uinfo,const char * uname)214 static gint cmp_irc_user_name_ignorecase(const user_info *uinfo, const char *uname)
215 {
216   gint result = -1;
217   irc_user_t *iu = (irc_user_t*)uinfo->user->ui_data;
218 
219   if (iu != NULL) {
220     gchar *cfn1 = g_utf8_casefold(iu->nick, -1);
221     gchar *cfn2 = g_utf8_casefold(uname, -1);
222 
223     result = g_strcmp0(cfn1, cfn2);
224 
225     g_free(cfn1);
226     g_free(cfn2);
227   }
228 
229   return result;
230 }
231 
cmp_server_id(const server_info * sinfo,const char * server_id)232 static gint cmp_server_id(const server_info *sinfo, const char *server_id)
233 {
234   return g_strcmp0(sinfo->id, server_id);
235 }
236 
get_server_by_id(discord_data * dd,const char * server_id)237 server_info *get_server_by_id(discord_data *dd, const char *server_id)
238 {
239   GSList *sl = g_slist_find_custom(dd->servers, server_id,
240                                    (GCompareFunc)cmp_server_id);
241 
242   return sl == NULL ?  NULL : sl->data;
243 }
244 
get_channel(discord_data * dd,const char * channel_id,const char * server_id,search_t type)245 channel_info *get_channel(discord_data *dd, const char *channel_id,
246                           const char *server_id, search_t type)
247 {
248   GSList *cl = NULL;
249   GCompareFunc sfunc = NULL;
250 
251   switch(type) {
252     case SEARCH_ID:
253       sfunc = (GCompareFunc)cmp_chan_id;
254       break;
255     case SEARCH_NAME:
256       sfunc = (GCompareFunc)cmp_chan_name;
257       break;
258     case SEARCH_NAME_IGNORECASE:
259       sfunc = (GCompareFunc)cmp_chan_name_ignorecase;
260       break;
261     case SEARCH_FNAME:
262       sfunc = (GCompareFunc)cmp_chan_fname;
263       break;
264     default:
265       return NULL;
266   }
267 
268   cl = g_slist_find_custom(dd->pchannels, channel_id, sfunc);
269 
270   if (cl == NULL) {
271     if (server_id != NULL) {
272       server_info *sinfo = get_server_by_id(dd, server_id);
273       cl = g_slist_find_custom(sinfo->channels, channel_id, sfunc);
274     } else {
275       for (GSList *sl = dd->servers; sl; sl = g_slist_next(sl)) {
276         server_info *sinfo = sl->data;
277         cl = g_slist_find_custom(sinfo->channels, channel_id, sfunc);
278         if (cl != NULL) {
279           break;
280         }
281       }
282     }
283   }
284 
285   return cl == NULL ?  NULL : cl->data;
286 }
287 
get_user(discord_data * dd,const char * uname,const char * server_id,search_t type)288 user_info *get_user(discord_data *dd, const char *uname,
289                     const char *server_id, search_t type)
290 {
291   GSList *ul = NULL;
292   GCompareFunc sfunc = NULL;
293 
294   switch(type) {
295     case SEARCH_ID:
296       sfunc = (GCompareFunc)cmp_user_id;
297       break;
298     case SEARCH_NAME:
299       sfunc = (GCompareFunc)cmp_user_name;
300       break;
301     case SEARCH_NAME_IGNORECASE:
302       sfunc = (GCompareFunc)cmp_user_name_ignorecase;
303       break;
304     case SEARCH_IRC_USER_NAME:
305       sfunc = (GCompareFunc)cmp_irc_user_name;
306       break;
307     case SEARCH_IRC_USER_NAME_IGNORECASE:
308       sfunc = (GCompareFunc)cmp_irc_user_name_ignorecase;
309       break;
310     default:
311       return NULL;
312   }
313 
314   if (server_id != NULL) {
315     server_info *sinfo = get_server_by_id(dd, server_id);
316     ul = g_slist_find_custom(sinfo->users, uname, sfunc);
317   } else {
318     for (GSList *sl = dd->servers; sl; sl = g_slist_next(sl)) {
319       server_info *sinfo = sl->data;
320       ul = g_slist_find_custom(sinfo->users, uname, sfunc);
321       if (ul != NULL) {
322         break;
323       }
324     }
325   }
326 
327   return ul == NULL ?  NULL : ul->data;
328 }
329 
discord_canonize_name(const char * name)330 char *discord_canonize_name(const char *name)
331 {
332   return str_reject_chars(g_strdup(name), "@+ ", '_');
333 }
334 
discord_escape(const GMatchInfo * match,GString * result,gpointer user_data)335 static gboolean discord_escape(const GMatchInfo *match, GString *result,
336                                gpointer user_data)
337 {
338   gchar *mstring = g_match_info_fetch(match, 0);
339   gchar *r = g_strdup_printf("\\%s", mstring);
340   result = g_string_append(result, r);
341   g_free(r);
342   g_free(mstring);
343 
344   return FALSE;
345 }
346 
discord_escape_string(const char * msg)347 char *discord_escape_string(const char *msg)
348 {
349   GRegex *escregex = g_regex_new("[\\\\\"]", 0, 0, NULL);
350   char *nmsg = NULL;
351   char *emsg = g_regex_replace_eval(escregex, msg, -1, 0, 0,
352                                      discord_escape, NULL, NULL);
353   g_regex_unref(escregex);
354 
355   escregex = g_regex_new("\t", 0, 0, NULL);
356   nmsg = g_regex_replace_literal(escregex, emsg, -1, 0, "\\t", 0, NULL);
357 
358   g_free(emsg);
359   emsg = nmsg;
360 
361   g_regex_unref(escregex);
362   escregex = g_regex_new("[\r\n]+", 0, 0, NULL);
363   nmsg = g_regex_replace_literal(escregex, emsg, -1, 0, "\\r\\n", 0, NULL);
364 
365   g_free(emsg);
366   emsg = nmsg;
367 
368   g_regex_unref(escregex);
369 
370   return emsg;
371 }
372 
discord_utf8_strndup(const char * str,size_t n)373 char *discord_utf8_strndup(const char *str, size_t n)
374 {
375   if (g_utf8_strlen(str, -1) <= n) {
376     return g_strdup(str);
377   }
378 
379   return g_strndup(str, g_utf8_offset_to_pointer(str, n) - str);
380 }
381 
parse_iso_8601(const char * timestamp)382 time_t parse_iso_8601(const char *timestamp)
383 {
384 #if GLIB_CHECK_VERSION(2,56,0)
385   if (!timestamp) return 0;
386   GDateTime *dt = g_date_time_new_from_iso8601(timestamp, NULL);
387   if (!dt) return 0;
388   gint64 unix = g_date_time_to_unix(dt);
389   g_date_time_unref(dt);
390   return unix;
391 #else
392   GTimeVal gt;
393   if (!timestamp) return 0;
394   if (!g_time_val_from_iso8601(timestamp, &gt)) return 0;
395   return gt.tv_sec;
396 #endif
397 }
398