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 <config.h>
18 #include <http_client.h>
19 #include <json.h>
20 #include <json_util.h>
21 
22 #include "discord.h"
23 #include "discord-http.h"
24 #include "discord-handlers.h"
25 #include "discord-websockets.h"
26 #include "discord-util.h"
27 
28 typedef struct _casm_data {
29   struct im_connection *ic;
30   char *msg;
31 } casm_data;
32 
33 typedef struct _mstr_data {
34   struct im_connection *ic;
35   char *sid;
36 } mstr_data;
37 
38 typedef struct _retry_req {
39   char *request;
40   struct im_connection *ic;
41   http_input_function func;
42   gpointer data;
43   gint evid;
44 } retry_req;
45 
_discord_http_get(struct im_connection * ic,char * request,http_input_function cb_func,gpointer data)46 static void _discord_http_get(struct im_connection *ic, char *request,
47                              http_input_function cb_func, gpointer data)
48 {
49   discord_data *dd = ic->proto_data;
50   struct http_request *req;
51 
52   req = http_dorequest(set_getstr(&ic->acc->set, "host"), 443, 1,
53                        request, cb_func, data);
54 
55   dd->pending_reqs = g_slist_prepend(dd->pending_reqs, req);
56 }
57 
discord_http_get(struct im_connection * ic,const char * api_path,http_input_function cb_func,gpointer data)58 static void discord_http_get(struct im_connection *ic, const char *api_path,
59                              http_input_function cb_func, gpointer data)
60 {
61   discord_data *dd = ic->proto_data;
62   GString *request = g_string_new("");
63   g_string_printf(request, "GET /api/%s HTTP/1.1\r\n"
64                   "Host: %s\r\n"
65                   "User-Agent: Bitlbee-Discord\r\n"
66                   "Content-Type: application/json\r\n"
67                   "authorization: %s\r\n\r\n",
68                   api_path,
69                   set_getstr(&ic->acc->set, "host"),
70                   dd->token);
71 
72   discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
73   _discord_http_get(ic, request->str, cb_func, data);
74   g_string_free(request, TRUE);
75 }
76 
discord_http_retry(retry_req * rreq,gint fd,b_input_condition cond)77 static gboolean discord_http_retry(retry_req *rreq, gint fd,
78                                    b_input_condition cond)
79 {
80   struct im_connection *ic = rreq->ic;
81   discord_data *dd = ic->proto_data;
82 
83   _discord_http_get(ic, rreq->request, rreq->func, rreq->data);
84 
85   dd->pending_events = g_slist_remove(dd->pending_events,
86                                      GINT_TO_POINTER(rreq->evid));
87   g_free(rreq->request);
88   g_free(rreq);
89 
90   return FALSE;
91 }
92 
discord_http_check_retry(struct http_request * req)93 static int discord_http_check_retry(struct http_request *req)
94 {
95   struct im_connection *ic = req->data;
96   discord_data *dd = ic->proto_data;
97 
98   if (req->status_code == 429) {
99     json_value *js = json_parse(req->reply_body, req->body_size);
100     if (!js || js->type != json_object) {
101       imcb_error(ic, "Error while parsing ratelimit message");
102       json_value_free(js);
103       return 0;
104     }
105 
106     json_value *retry = json_o_get(js, "retry_after");
107     guint32 timeout = (retry && retry->type == json_integer) ? retry->u.integer : 0;
108     retry_req *rreq = g_new0(retry_req, 1);
109     rreq->request = g_strdup(req->request);
110     rreq->ic = ic;
111     rreq->func = req->func;
112     rreq->data = req->data;
113 
114     gint evid = b_timeout_add(timeout, (b_event_handler)discord_http_retry,
115                               rreq);
116     rreq->evid = evid;
117 
118     dd->pending_events = g_slist_prepend(dd->pending_events,
119                                          GINT_TO_POINTER(evid));
120     discord_debug("(%s) %s [%d] retry scheduled in %u", dd->uname, __func__,
121                   evid, timeout);
122 
123     json_value_free(js);
124     return 1;
125   }
126   return 0;
127 }
128 
discord_http_gateway_cb(struct http_request * req)129 static void discord_http_gateway_cb(struct http_request *req)
130 {
131   struct im_connection *ic = req->data;
132   discord_data *dd = ic->proto_data;
133 
134   discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
135                 req->status_code, req->body_size, req->reply_body);
136 
137   dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
138 
139   if (req->status_code == 200) {
140     json_value *js = json_parse(req->reply_body, req->body_size);
141     if (!js || js->type != json_object) {
142       imcb_error(ic, "Failed to parse json reply (%s)", __func__);
143       imc_logout(ic, TRUE);
144       json_value_free(js);
145       return;
146     }
147     discord_data *dd = ic->proto_data;
148 
149     const char *gw = json_o_str(js, "url");
150     GMatchInfo *match = NULL;
151     GRegex *gwregex = g_regex_new("^(wss?://)?([^/]+)(/.*)?$", 0, 0, NULL);
152 
153     g_regex_match(gwregex, gw, 0, &match);
154 
155     if (match == NULL) {
156       imcb_error(ic, "Failed to get gateway (%s).", gw);
157       json_value_free(js);
158       g_regex_unref(gwregex);
159       imc_logout(ic, TRUE);
160       return;
161     }
162 
163     dd->gateway = g_new0(gw_data, 1);
164 
165     gchar *wss = g_match_info_fetch(match, 1);
166     if (g_strcmp0(wss, "wss://") == 0) {
167       dd->gateway->wss = 1;
168     } else {
169       dd->gateway->wss = 0;
170     }
171     g_free(wss);
172 
173     dd->gateway->addr = g_match_info_fetch(match, 2);
174     dd->gateway->path = g_match_info_fetch(match, 3);
175 
176     if (dd->gateway->path == NULL) {
177       dd->gateway->path = g_strdup("/?encoding=json&v=6");
178     } else if (g_strcmp0(dd->gateway->path, "") == 0) {
179       g_free(dd->gateway->path);
180       dd->gateway->path = g_strdup("/?encoding=json&v=6");
181     }
182 
183     g_match_info_free(match);
184     g_regex_unref(gwregex);
185 
186     if (discord_ws_init(ic, dd) < 0) {
187       imcb_error(ic, "Failed to create websockets context.");
188       imc_logout(ic, TRUE);
189       json_value_free(js);
190       return;
191     }
192     dd->state = WS_CONNECTING;
193 
194     json_value_free(js);
195   } else {
196     if (discord_http_check_retry(req) == 0) {
197       imcb_error(ic, "Failed to get info about self.");
198       imc_logout(ic, TRUE);
199     }
200   }
201 }
202 
discord_http_get_gateway(struct im_connection * ic,const char * token)203 void discord_http_get_gateway(struct im_connection *ic, const char *token)
204 {
205   discord_data *dd = ic->proto_data;
206 
207   dd->token = g_strdup(token);
208   set_setstr(&ic->acc->set, "token_cache", dd->token);
209   discord_http_get(ic, "gateway", discord_http_gateway_cb, ic);
210 }
211 
discord_http_mfa_cb(struct http_request * req)212 static void discord_http_mfa_cb(struct http_request *req)
213 {
214   struct im_connection *ic = req->data;
215   discord_data *dd = ic->proto_data;
216 
217   discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
218                 req->status_code, req->body_size, req->reply_body);
219   dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
220 
221   json_value *js = json_parse(req->reply_body, req->body_size);
222   if (!js || js->type != json_object) {
223     imcb_error(ic, "Failed to parse json reply (%s)", __func__);
224     imc_logout(ic, TRUE);
225     json_value_free(js);
226     return;
227   }
228 
229   imcb_remove_buddy(ic, DISCORD_MFA_HANDLE, NULL);
230   if (req->status_code == 200) {
231     discord_data *dd = ic->proto_data;
232 
233     g_free(dd->token);
234     discord_http_get_gateway(ic, json_o_str(js, "token"));
235   } else {
236     if (discord_http_check_retry(req) == 0) {
237       imcb_error(ic, "MFA Error: %s", (char*)json_o_str(js, "message"));
238       imc_logout(ic, TRUE);
239     }
240   }
241   json_value_free(js);
242 }
243 
discord_http_login_cb(struct http_request * req)244 static void discord_http_login_cb(struct http_request *req)
245 {
246   struct im_connection *ic = req->data;
247   discord_data *dd = ic->proto_data;
248 
249   discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
250                 req->status_code, req->body_size, req->reply_body);
251   dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
252 
253   json_value *js = json_parse(req->reply_body, req->body_size);
254   if (!js || js->type != json_object) {
255     imcb_error(ic, "Failed to parse json reply (%s)", __func__);
256     imc_logout(ic, TRUE);
257     json_value_free(js);
258     return;
259   }
260 
261   if (req->status_code == 200) {
262     discord_data *dd = ic->proto_data;
263     json_value *mfa = json_o_get(js, "mfa");
264 
265     if (mfa != NULL && mfa->type == json_boolean && mfa->u.boolean == TRUE) {
266       dd->token = json_o_strdup(js, "ticket");
267       imcb_log(ic, "Starting MFA authentication");
268       imcb_add_buddy(ic, DISCORD_MFA_HANDLE, NULL);
269       imcb_buddy_msg(ic, DISCORD_MFA_HANDLE, "Two-factor auth is enabled. "
270                      "Please respond to this message with your token.", 0, 0);
271     } else {
272       discord_http_get_gateway(ic, json_o_str(js, "token"));
273     }
274   } else {
275     if (discord_http_check_retry(req) == 0) {
276       char *errmsg = (char*)json_o_str(js, "message");
277 
278       if (errmsg == NULL) {
279         json_value *em = NULL;
280         json_value *email = json_o_get(js, "email");
281         json_value *password = json_o_get(js, "password");
282         json_value *captcha_key = json_o_get(js, "captcha_key");
283 
284         if (email != NULL && email->type == json_array) {
285           em = email->u.array.values[0];
286         } else if (password != NULL && password->type == json_array) {
287           em = password->u.array.values[0];
288         } else if (captcha_key != NULL && captcha_key->type == json_array) {
289           em = captcha_key->u.array.values[0];
290         }
291 
292         if (em != NULL && em->type == json_string) {
293           errmsg = em->u.string.ptr;
294         }
295       }
296 
297       imcb_error(ic, "Login error: %s", errmsg);
298       imc_logout(ic, TRUE);
299     }
300   }
301   json_value_free(js);
302 }
303 
discord_http_noop_cb(struct http_request * req)304 static void discord_http_noop_cb(struct http_request *req)
305 {
306   discord_data *dd = req->data;
307   dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
308   return;
309 }
310 
discord_http_send_msg_cb(struct http_request * req)311 static void discord_http_send_msg_cb(struct http_request *req)
312 {
313   struct im_connection *ic = req->data;
314   discord_data *dd = ic->proto_data;
315 
316   dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
317 
318   discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
319                 req->status_code, req->body_size, req->reply_body);
320 
321   if (req->status_code != 200) {
322     if (discord_http_check_retry(req) == 0) {
323       char *json_text = strstr(req->request, "{\"content\":\"");
324       json_value *js = json_parse(json_text, strlen(json_text));
325       const char *message = json_o_str(js, "content");
326 
327       imcb_error(ic, "Failed to send message (%d; %s).", req->status_code, message);
328       json_value_free(js);
329     }
330   }
331 }
332 
discord_http_backlog_cb(struct http_request * req)333 static void discord_http_backlog_cb(struct http_request *req)
334 {
335   struct im_connection *ic = req->data;
336   discord_data *dd = ic->proto_data;
337 
338   dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
339 
340   discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
341                 req->status_code, req->body_size, req->reply_body);
342 
343   if (req->status_code != 200) {
344     if (discord_http_check_retry(req) == 0) {
345       imcb_error(ic, "Failed to get backlog (%d).", req->status_code);
346     }
347   } else {
348     json_value *messages = json_parse(req->reply_body, req->body_size);
349     if (!messages || messages->type != json_array) {
350       imcb_error(ic, "Failed to parse json reply (%s)", __func__);
351       imc_logout(ic, TRUE);
352       json_value_free(messages);
353       return;
354     }
355 
356     for (int midx = messages->u.array.length - 1; midx >= 0; midx--) {
357       json_value *minfo = messages->u.array.values[midx];
358       discord_handle_message(ic, minfo, ACTION_CREATE, TRUE);
359     }
360 
361     json_value_free(messages);
362   }
363 }
364 
discord_http_get_backlog(struct im_connection * ic,const char * channel_id)365 void discord_http_get_backlog(struct im_connection *ic, const char *channel_id)
366 {
367   GString *api = g_string_new("");
368 
369   g_string_printf(api, "channels/%s/messages?limit=%d", channel_id,
370                   set_getint(&ic->acc->set, "max_backlog"));
371 
372   discord_http_get(ic, api->str, discord_http_backlog_cb, ic);
373 
374   g_string_free(api, TRUE);
375 }
376 
discord_http_pinned_cb(struct http_request * req)377 static void discord_http_pinned_cb(struct http_request *req)
378 {
379   struct im_connection *ic = req->data;
380   discord_data *dd = ic->proto_data;
381 
382   dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
383 
384   discord_debug("<<< (%s) %s [%d] %d\n%s\n", dd->uname, __func__,
385                 req->status_code, req->body_size, req->reply_body);
386 
387   if (req->status_code != 200) {
388     if (discord_http_check_retry(req) == 0) {
389       imcb_error(ic, "Failed to get pinned messages (%d).", req->status_code);
390     }
391   } else {
392     json_value *messages = json_parse(req->reply_body, req->body_size);
393     if (!messages || messages->type != json_array) {
394       imcb_error(ic, "Failed to parse json reply (%s)", __func__);
395       imc_logout(ic, TRUE);
396       json_value_free(messages);
397       return;
398     }
399 
400     for (int midx = messages->u.array.length - 1; midx >= 0; midx--) {
401       json_value *minfo = messages->u.array.values[midx];
402       discord_handle_message(ic, minfo, ACTION_CREATE, TRUE);
403     }
404 
405     json_value_free(messages);
406   }
407 }
408 
discord_http_get_pinned(struct im_connection * ic,const char * channel_id)409 void discord_http_get_pinned(struct im_connection *ic, const char *channel_id)
410 {
411   GString *api = g_string_new("");
412 
413   g_string_printf(api, "channels/%s/pins", channel_id);
414 
415   discord_http_get(ic, api->str, discord_http_pinned_cb, ic);
416 
417   g_string_free(api, TRUE);
418 }
419 
discord_mentions_string(const GMatchInfo * match,GString * result,gpointer user_data)420 static gboolean discord_mentions_string(const GMatchInfo *match,
421                                         GString *result,
422                                         gpointer user_data)
423 {
424   mstr_data *md = (mstr_data *)user_data;
425   struct im_connection *ic = md->ic;
426   discord_data *dd = ic->proto_data;
427   gchar *name = g_match_info_fetch(match, 1);
428 
429   search_t stype = SEARCH_IRC_USER_NAME;
430   if (set_getbool(&ic->acc->set, "mention_ignorecase") == TRUE) {
431     stype = SEARCH_IRC_USER_NAME_IGNORECASE;
432   }
433 
434   user_info *uinfo = get_user(dd, name, md->sid, stype);
435   g_free(name);
436 
437   if (uinfo != NULL) {
438     gchar *id = g_strdup_printf("<@%s>", uinfo->id);
439     result = g_string_append(result, id);
440     g_free(id);
441   } else {
442     gchar *fmatch = g_match_info_fetch(match, 0);
443     result = g_string_append(result, fmatch);
444     g_free(fmatch);
445   }
446 
447   return FALSE;
448 }
449 
discord_channel_string(const GMatchInfo * match,GString * result,gpointer user_data)450 static gboolean discord_channel_string(const GMatchInfo *match,
451                                        GString *result,
452                                        gpointer user_data)
453 {
454   mstr_data *md = (mstr_data *)user_data;
455   struct im_connection *ic = md->ic;
456   discord_data *dd = ic->proto_data;
457 
458   gchar *name = g_match_info_fetch(match, 1);
459 
460   search_t stype = SEARCH_NAME;
461   if (set_getbool(&ic->acc->set, "mention_ignorecase") == TRUE) {
462     stype = SEARCH_NAME_IGNORECASE;
463   }
464 
465   channel_info *cinfo = get_channel(dd, name, md->sid, stype);
466   g_free(name);
467 
468   if (cinfo != NULL) {
469     gchar *id = g_strdup_printf("<#%s>", cinfo->id);
470     result = g_string_append(result, id);
471     g_free(id);
472   } else {
473     gchar *fmatch = g_match_info_fetch(match, 0);
474     result = g_string_append(result, fmatch);
475     g_free(fmatch);
476   }
477 
478   return FALSE;
479 }
480 
discord_http_send_msg(struct im_connection * ic,const char * id,const char * msg)481 void discord_http_send_msg(struct im_connection *ic, const char *id,
482                            const char *msg)
483 {
484   discord_data *dd = ic->proto_data;
485   GString *request = g_string_new("");
486   GString *content = g_string_new("");
487   channel_info *cinfo = get_channel(dd, id, NULL, SEARCH_ID);
488   mstr_data *md = g_new0(mstr_data, 1);
489 
490   md->ic = ic;
491   if (cinfo != NULL && cinfo->type == CHANNEL_TEXT) {
492     md->sid = cinfo->to.channel.sinfo->id;
493   }
494 
495   gchar *nmsg = NULL;
496   gchar *emsg = discord_escape_string(msg);
497 
498   if (strlen(set_getstr(&ic->acc->set,"mention_suffix")) > 0) {
499     gchar *hlrstr = g_strdup_printf("(\\S+)%s", set_getstr(&ic->acc->set,
500                                                 "mention_suffix"));
501     GRegex *hlregex = g_regex_new(hlrstr, 0, 0, NULL);
502 
503     g_free(hlrstr);
504     nmsg = g_regex_replace_eval(hlregex, emsg, -1, 0, 0,
505                                 discord_mentions_string, md, NULL);
506     g_free(emsg);
507     emsg = nmsg;
508     g_regex_unref(hlregex);
509   }
510 
511   GRegex *hlregex = g_regex_new("@(\\S+)", 0, 0, NULL);
512 
513   nmsg = g_regex_replace_eval(hlregex, emsg, -1, 0, 0,
514                               discord_mentions_string, md, NULL);
515   g_free(emsg);
516   emsg = nmsg;
517   g_regex_unref(hlregex);
518 
519   hlregex = g_regex_new("#(\\S+)", 0, 0, NULL);
520   nmsg = g_regex_replace_eval(hlregex, emsg, -1, 0, 0,
521                               discord_channel_string, md, NULL);
522   g_free(emsg);
523   emsg = nmsg;
524   g_regex_unref(hlregex);
525   g_free(md);
526 
527   if (g_str_has_prefix(emsg, "/me ")) {
528     nmsg = g_strdup_printf("_%s_", emsg + 4);
529     g_free(emsg);
530     emsg = nmsg;
531   }
532 
533   gchar *nonce;
534   guchar nonce_bytes[16];
535 
536   random_bytes(nonce_bytes, sizeof(nonce_bytes));
537   nonce = g_base64_encode(nonce_bytes, sizeof(nonce_bytes));
538   g_hash_table_insert(dd->sent_message_ids, nonce,
539       GUINT_TO_POINTER((guint)time(NULL)));
540   g_string_printf(content, "{\"content\":\"%s\", \"nonce\":\"%s\"}",
541                   emsg, nonce);
542   g_free(emsg);
543   g_string_printf(request, "POST /api/channels/%s/messages HTTP/1.1\r\n"
544                   "Host: %s\r\n"
545                   "User-Agent: Bitlbee-Discord\r\n"
546                   "authorization: %s\r\n"
547                   "Content-Type: application/json\r\n"
548                   "Content-Length: %zd\r\n\r\n"
549                   "%s",
550                   id,
551                   set_getstr(&ic->acc->set, "host"),
552                   dd->token,
553                   content->len,
554                   content->str);
555 
556   discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
557 
558   _discord_http_get(ic, request->str, discord_http_send_msg_cb, ic);
559 
560   g_string_free(content, TRUE);
561   g_string_free(request, TRUE);
562 }
563 
discord_http_send_ack(struct im_connection * ic,const char * channel_id,const char * message_id)564 void discord_http_send_ack(struct im_connection *ic, const char *channel_id,
565                            const char *message_id)
566 {
567   if (set_getbool(&ic->acc->set, "send_acks") == FALSE) {
568     return;
569   }
570 
571   discord_data *dd = ic->proto_data;
572   GString *request = g_string_new("");
573 
574   g_string_printf(request, "POST /api/channels/%s/messages/%s/ack HTTP/1.1\r\n"
575                   "Host: %s\r\n"
576                   "User-Agent: Bitlbee-Discord\r\n"
577                   "Content-Type: application/json\r\n"
578                   "Authorization: %s\r\n"
579                   "Content-Length: 2\r\n\r\n"
580                   "{}",
581                   channel_id, message_id,
582                   set_getstr(&ic->acc->set, "host"),
583                   dd->token);
584 
585   discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
586 
587   _discord_http_get(ic, request->str, discord_http_noop_cb, dd);
588 
589   g_string_free(request, TRUE);
590 }
591 
discord_http_mfa_auth(struct im_connection * ic,const char * msg)592 void discord_http_mfa_auth(struct im_connection *ic, const char *msg)
593 {
594   GString *request = g_string_new("");
595   GString *auth = g_string_new("");
596   discord_data *dd = ic->proto_data;
597 
598   g_string_printf(auth, "{\"code\":\"%s\",\"ticket\":\"%s\"}",
599                   msg,
600                   dd->token);
601 
602   g_string_printf(request, "POST /api/auth/mfa/totp HTTP/1.1\r\n"
603                   "Host: %s\r\n"
604                   "User-Agent: Bitlbee-Discord\r\n"
605                   "Content-Type: application/json\r\n"
606                   "Content-Length: %zd\r\n\r\n"
607                   "%s",
608                   set_getstr(&ic->acc->set, "host"),
609                   auth->len,
610                   auth->str);
611 
612   discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
613 
614   _discord_http_get(ic, request->str, discord_http_mfa_cb, ic);
615 
616   g_string_free(auth, TRUE);
617   g_string_free(request, TRUE);
618 }
619 
discord_http_login(account_t * acc)620 void discord_http_login(account_t *acc)
621 {
622   GString *request = g_string_new("");
623   GString *jlogin = g_string_new("");
624   gchar *epass = discord_escape_string(acc->pass);
625   discord_data *dd = acc->ic->proto_data;
626 
627   g_string_printf(jlogin, "{\"email\":\"%s\",\"password\":\"%s\"}",
628                   acc->user,
629                   epass);
630 
631   g_string_printf(request, "POST /api/auth/login HTTP/1.1\r\n"
632                   "Host: %s\r\n"
633                   "User-Agent: Bitlbee-Discord\r\n"
634                   "Content-Type: application/json\r\n"
635                   "Content-Length: %zd\r\n\r\n"
636                   "%s",
637                   set_getstr(&acc->set, "host"),
638                   jlogin->len,
639                   jlogin->str);
640 
641   discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
642 
643   _discord_http_get(acc->ic, request->str, discord_http_login_cb, acc->ic);
644 
645   g_free(epass);
646   g_string_free(jlogin, TRUE);
647   g_string_free(request, TRUE);
648 }
649 
discord_http_casm_cb(struct http_request * req)650 static void discord_http_casm_cb(struct http_request *req)
651 {
652   casm_data *cd = req->data;
653   struct im_connection *ic = cd->ic;
654   discord_data *dd = ic->proto_data;
655   dd->pending_reqs = g_slist_remove(dd->pending_reqs, req);
656   if (req->status_code != 200) {
657     if (discord_http_check_retry(req) == 0) {
658       imcb_error(ic, "Failed to create private channel (%d).",
659                  req->status_code);
660     }
661     goto out;
662   }
663 
664   json_value *channel = json_parse(req->reply_body, req->body_size);
665   if (!channel || channel->type != json_object) {
666     imcb_error(ic, "Failed to create private channel.");
667     goto jout;
668   }
669 
670   discord_handle_channel(ic, channel, NULL, ACTION_CREATE);
671   discord_http_send_msg(ic, json_o_str(channel, "id"), cd->msg);
672 
673 jout:
674   json_value_free(channel);
675 
676 out:
677   g_free(cd->msg);
678   g_free(cd);
679 }
680 
discord_http_create_and_send_msg(struct im_connection * ic,const char * handle,const char * msg)681 void discord_http_create_and_send_msg(struct im_connection *ic,
682                                       const char *handle, const char *msg)
683 {
684   discord_data *dd = ic->proto_data;
685   user_info *uinfo = get_user(dd, handle, NULL, SEARCH_IRC_USER_NAME);
686 
687   if (uinfo == NULL) {
688     imcb_error(ic, "Failed to create channel for unknown user: '%s'.",
689                handle);
690     return;
691   }
692 
693   GString *request = g_string_new("");
694   GString *content = g_string_new("");
695 
696   g_string_printf(content, "{\"recipient_id\":\"%s\"}", uinfo->id);
697   g_string_printf(request, "POST /api/users/%s/channels HTTP/1.1\r\n"
698                   "Host: %s\r\n"
699                   "User-Agent: Bitlbee-Discord\r\n"
700                   "authorization: %s\r\n"
701                   "Content-Type: application/json\r\n"
702                   "Content-Length: %zd\r\n\r\n"
703                   "%s",
704                   dd->id,
705                   set_getstr(&ic->acc->set, "host"),
706                   dd->token,
707                   content->len,
708                   content->str);
709 
710   casm_data *cd = g_new0(casm_data, 1);
711   cd->ic = ic;
712   cd->msg = g_strdup(msg);
713 
714   discord_debug(">>> (%s) %s %lu", dd->uname, __func__, request->len);
715 
716   _discord_http_get(ic, request->str, discord_http_casm_cb, cd);
717 
718   g_string_free(content, TRUE);
719   g_string_free(request, TRUE);
720 }
721