2 * Copyright (c) 2018, De Rais <derais@cock.li>
4 * Permission to use, copy, modify, and/or distribute this software for
5 * any purpose with or without fee is hereby granted, provided that the
6 * above copyright notice and this permission notice appear in all
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
14 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16 * PERFORMANCE OF THIS SOFTWARE.
22 #include <curl/curl.h>
23 #include <libwebsockets.h>
24 #include <yajl/yajl_tree.h>
30 static struct lws_client_connect_info
*cci
;
32 /* LWS userdata is insane, like everything else. Screw it */
33 struct state
volatile *main_s
;
37 static char **cookies
;
39 /* Hurrah, another layer of indirection */
40 struct real_server_ud
{
42 struct state
volatile *s
;
49 curl_drop(char *buf
, size_t size
, size_t nmemb
, void *userdata
)
57 /* Rip sid out of a JSON-esque reply */
59 cb_extract_sid_and_ping(char *buf
, size_t size
, size_t nmemb
, void *userdata
)
62 size_t len
= size
* nmemb
;
66 char *pingInt_start
= 0;
67 size_t pingInt_off
= 0;
68 struct state
volatile *s
= (struct state
volatile *) userdata
;
70 if (len
/ size
!= nmemb
) {
71 ERROR_MESSAGE("overflow");
75 for (sid_off
= 0; sid_off
+ 7 < len
; ++sid_off
) {
76 if (!strncmp(buf
+ sid_off
, "\"sid\":\"", 7)) {
78 sid_start
= buf
+ sid_off
;
87 for (sid_len
= 0; sid_off
+ sid_len
< len
; ++sid_len
) {
88 if (sid_start
[sid_len
] == '"') {
89 if (!(s
->sid
= strndup(sid_start
, sid_len
))) {
90 PERROR_MESSAGE("strndup");
98 for (pingInt_off
= 0; pingInt_off
+ 15 < len
; ++pingInt_off
) {
99 if (!strncmp(buf
+ pingInt_off
, "\"pingInterval\":", 15)) {
101 pingInt_start
= buf
+ pingInt_off
;
105 if (!pingInt_start
) {
109 s
->ping_interval
= strtoll(pingInt_start
, 0, 10);
117 /* snprintf + malloc + sprintf */
119 perform_lws_write(struct lws
*wsi
, const char *format
, ...)
126 va_start(arglist
, format
);
127 len
= vsnprintf(0, 0, format
, arglist
);
130 if (!(mem
= malloc(len
+ LWS_PRE
+ 1))) {
134 va_start(arglist
, format
);
135 vsprintf(mem
+ LWS_PRE
, format
, arglist
);
137 lws_write(wsi
, (unsigned char *) (mem
+ LWS_PRE
), len
, LWS_WRITE_TEXT
);
145 /* Follow the breadcrumb trail of servers */
147 cb_get_real_server(char *buf
, size_t size
, size_t nmemb
, void *userdata
)
150 size_t len
= size
* nmemb
;
151 struct real_server_ud
*rsu
= (struct real_server_ud
*) userdata
;
152 size_t old_len
= rsu
->chunk_len
;
156 yajl_val servers
= 0;
157 char eb
[1024] = { 0 };
158 const char *url_path
[] = { "url", 0 };
159 const char *servers_path
[] = { "servers", 0 };
161 if (len
/ size
!= nmemb
) {
162 ERROR_MESSAGE("overflow");
166 if (SSIZE_MAX
- len
- 1 < rsu
->chunk_len
) {
167 ERROR_MESSAGE("overflow");
171 new_len
= rsu
->chunk_len
+ len
;
173 if (!(newmem
= realloc(rsu
->chunk
, new_len
+ 1))) {
174 PERROR_MESSAGE("malloc");
179 rsu
->chunk_len
= new_len
;
180 memcpy(rsu
->chunk
+ old_len
, buf
, len
);
181 rsu
->chunk
[new_len
] = 0;
184 /* See handle_state: we're trying to save some memleaks in yajl */
185 if (rsu
->chunk
[new_len
- 1] != '}') {
189 tree
= yajl_tree_parse(rsu
->chunk
, eb
, 1024);
196 !(servers
= yajl_tree_get(tree
, servers_path
, yajl_t_array
))) {
200 if (!(YAJL_IS_ARRAY(servers
))) {
204 for (size_t k
= 0; k
< YAJL_GET_ARRAY(servers
)->len
; ++k
) {
205 yajl_val e
= YAJL_GET_ARRAY(servers
)->values
[k
];
206 const char *url
= YAJL_GET_STRING(yajl_tree_get(e
, url_path
,
213 if ((rsu
->s
->https
&&
214 !strncmp(url
, "https://", 8)) ||
216 !strncmp(url
, "http://", 7))) {
217 free(rsu
->s
->socket_host
);
218 rsu
->s
->socket_host
= 0;
220 if (!(rsu
->s
->socket_host
= strdup(url
))) {
229 yajl_tree_free(tree
);
234 /* Rip ip-session cookie out of headers */
236 cb_save_headers(char *buf
, size_t size
, size_t nmemb
, void *userdata
)
239 size_t len
= size
* nmemb
;
240 size_t cookie_len
= 0;
241 char *cookie_start
= 0;
246 if (len
/ size
!= nmemb
) {
247 ERROR_MESSAGE("overflow");
251 /* buf had better contain the whole Set-Cookie: stuff */
252 if (!(cookie_start
= strstr(buf
, "Set-Cookie: "))) {
256 cookie_start
+= strlen("Set-Cookie: ");
258 while (cookie_start
[cookie_len
] &&
259 cookie_start
[cookie_len
] != ';') {
263 if (cookie_start
[cookie_len
] != ';') {
264 ERROR_MESSAGE("ip-session cookie unterminated -- bailing");
268 cookie_start
[cookie_len
] = 0;
270 if (!(newmem
= realloc(cookies
, (cookies_sz
+ 1) * sizeof *cookies
))) {
271 PERROR_MESSAGE("realloc");
278 if (!(cookies
[cookies_sz
- 1] = strdup(cookie_start
))) {
279 PERROR_MESSAGE("strdup");
289 /* Extract the ip-session cookie that sync demands */
292 cytube_get_session_cookie(const char *server
, const char *protocol
, const
293 char *channel
, struct state
volatile *s
)
296 Grumble grumble -- we only need libcurl for one request,
297 to get the ip-session cookie, and we *could* parse that
298 ourselves except for the fact that everything redirects
299 to https and *ssl is a pita.
303 char curl_error
[CURL_ERROR_SIZE
];
305 struct real_server_ud rsu
= { .s
= s
};
309 if (!(ch
= curl_easy_init())) {
310 ERROR_MESSAGE("curl_easy_init failed");
314 /* XXX: THESE ARE NOT NEEDED YOU JUST NEED SID */
315 /* First, grab the _csrf and ip-session cookies */
316 if (!(url
= aprintf("%s://%s/r/%s", protocol
, server
, channel
))) {
317 PERROR_MESSAGE("aprintf");
321 curl_easy_setopt(ch
, CURLOPT_URL
, url
);
322 curl_easy_setopt(ch
, CURLOPT_NOBODY
, 1L);
323 curl_easy_setopt(ch
, CURLOPT_FOLLOWLOCATION
, 2L);
324 curl_easy_setopt(ch
, CURLOPT_MAXREDIRS
, 2L);
325 curl_easy_setopt(ch
, CURLOPT_HEADERFUNCTION
, cb_save_headers
);
326 curl_easy_setopt(ch
, CURLOPT_WRITEFUNCTION
, curl_drop
);
327 curl_easy_setopt(ch
, CURLOPT_ERRORBUFFER
, &curl_error
);
329 if (curl_easy_perform(ch
) != CURLE_OK
) {
330 ERROR_MESSAGE("curl_easy_perform: %s", curl_error
);
334 /* Now, grab the real server */
337 if (!(url
= aprintf("%s://%s/socketconfig/%s.json", protocol
, server
,
339 PERROR_MESSAGE("aprintf");
343 curl_easy_setopt(ch
, CURLOPT_URL
, url
);
344 curl_easy_setopt(ch
, CURLOPT_NOBODY
, 0L);
345 curl_easy_setopt(ch
, CURLOPT_FOLLOWLOCATION
, 2L);
346 curl_easy_setopt(ch
, CURLOPT_MAXREDIRS
, 2L);
347 curl_easy_setopt(ch
, CURLOPT_WRITEDATA
, (void *) &rsu
);
348 curl_easy_setopt(ch
, CURLOPT_WRITEFUNCTION
, cb_get_real_server
);
349 curl_easy_setopt(ch
, CURLOPT_HEADERFUNCTION
, curl_drop
);
350 curl_easy_setopt(ch
, CURLOPT_ERRORBUFFER
, &curl_error
);
352 if (curl_easy_perform(ch
) != CURLE_OK
) {
353 ERROR_MESSAGE("curl_easy_perform: %s", curl_error
);
358 rsu
= (struct real_server_ud
) { 0 };
360 /* Now, connect to the socket.io thing to get the sid */
363 if (!(url
= aprintf("%s/socket.io/?EIO=3&transport=polling",
365 PERROR_MESSAGE("aprintf");
369 curl_easy_setopt(ch
, CURLOPT_URL
, url
);
370 curl_easy_setopt(ch
, CURLOPT_WRITEFUNCTION
, cb_extract_sid_and_ping
);
371 curl_easy_setopt(ch
, CURLOPT_WRITEDATA
, (void *) s
);
373 if (curl_easy_perform(ch
) != CURLE_OK
) {
374 ERROR_MESSAGE("curl_easy_perform: %s", curl_error
);
379 ERROR_MESSAGE("/socket.io did not return sid -- bailing");
383 if (s
->ping_interval
< 200) {
384 s
->ping_interval
= 200;
392 curl_easy_cleanup(ch
);
395 curl_global_cleanup();
400 /* Deal with something to do with lws */
402 cytube_lws_handler(struct lws
*wsi
, enum lws_callback_reasons reason
,
403 void *user
, void *in
, size_t len
)
405 unsigned char **p
= 0;
406 unsigned char *end
= 0;
409 struct state
volatile *s
= main_s
;
414 ERROR_MESSAGE("cci is somehow not set");
418 ERROR_MESSAGE("s is somehow not set");
422 case LWS_CALLBACK_PROTOCOL_INIT
:
425 "/socket.io/?EIO=3&transport=websocket&sid=%s",
427 ERROR_MESSAGE("aprintf");
433 lws_client_connect_via_info(cci
);
437 case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER
:
438 p
= (unsigned char **) in
;
441 for (size_t k
= 0; k
< cookies_sz
; ++k
) {
442 if (lws_add_http_header_by_token( /* */
443 wsi
, WSI_TOKEN_HTTP_COOKIE
, /* */
444 (unsigned char *) cookies
[k
], /* */
445 strlen(cookies
[k
]), p
, end
)) {
451 case LWS_CALLBACK_CLIENT_ESTABLISHED
:
455 s
->must_write_upgrade
= 1;
456 s
->must_join_channel
= 1;
457 lws_callback_on_writable(wsi
);
459 case LWS_CALLBACK_CLIENT_WRITEABLE
:
461 if (s
->must_write_upgrade
) {
462 /* I have no idea why we have to do this */
463 perform_lws_write(wsi
, "5");
464 s
->must_write_upgrade
= 0;
466 } else if (s
->must_write_ping
) {
467 perform_lws_write(wsi
, "2");
468 s
->must_write_ping
= 0;
470 } else if (s
->must_join_channel
) {
471 perform_lws_write(wsi
,
472 "42[\"joinChannel\", {\"name\": \"%s\"}]",
474 s
->must_join_channel
= 0;
476 /* We have to wait until we've fully joined to ask for the playlist */
478 } else if (s
->must_ask_for_playlist
) {
479 perform_lws_write(wsi
, "42[\"requestPlaylist\"]");
480 s
->must_ask_for_playlist
= 0;
485 case LWS_CALLBACK_CLIENT_RECEIVE
:
486 str
= (const char *) in
;
490 s
->stored_cmd_len
>= (1 << 25)) {
491 /* This is a PONG. Scrap whatever incompletes we've got */
494 s
->stored_cmd_len
= 0;
495 } else if (s
->stored_cmd
) {
496 /* We're continuing an incomplete message, right? */
499 if (!(newmem
= realloc(s
->stored_cmd
,
500 s
->stored_cmd_len
+ len
))) {
503 s
->stored_cmd_len
= 0;
505 s
->stored_cmd
= newmem
;
506 memcpy(s
->stored_cmd
+ s
->stored_cmd_len
, str
,
508 s
->stored_cmd_len
+= len
;
510 if (state_handle(s
, s
->stored_cmd
,
511 s
->stored_cmd_len
) >= 0) {
514 s
->stored_cmd_len
= 0;
517 } else if (len
> 2 &&
521 if (state_handle(s
, str
+ 2, len
- 2) < 0) {
522 /* Perhaps this is the start of an incomplete message */
523 if ((s
->stored_cmd
= strndup(str
+ 2, len
-
525 s
->stored_cmd_len
= len
- 2;
530 /* HANDLE MESSAGES HERE */
532 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR
:
533 ERROR_MESSAGE("LWS error: %*s", (int) len
, in
? (char *) in
:
537 case LWS_CALLBACK_PROTOCOL_DESTROY
:
538 case LWS_CALLBACK_WSI_DESTROY
:
541 case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS
:
542 case LWS_CALLBACK_GET_THREAD_ID
:
543 case LWS_CALLBACK_ADD_POLL_FD
:
544 case LWS_CALLBACK_WSI_CREATE
:
545 case LWS_CALLBACK_EVENT_WAIT_CANCELLED
:
546 case LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION
:
547 case LWS_CALLBACK_LOCK_POLL
:
548 case LWS_CALLBACK_UNLOCK_POLL
:
549 case LWS_CALLBACK_CHANGE_MODE_POLL_FD
:
550 case LWS_CALLBACK_DEL_POLL_FD
:
551 case LWS_CALLBACK_VHOST_CERT_AGING
:
555 /* We get 2 on startup (?) and 75 on destroy (?) */
556 /* ERROR_MESSAGE("unknown lws response; reason = %d", reason); */
563 /* Pass in the cci for use in handler */
565 cytube_set_cci_and_s(struct lws_client_connect_info
*ccip
, struct state