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, >)) return 0;
395 return gt.tv_sec;
396 #endif
397 }
398