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
;
48 static size_t curl_drop(char *buf
, size_t size
, size_t nmemb
, void *userdata
)
56 /* Rip sid out of a JSON-esque reply */
57 static size_t cb_extract_sid_and_ping(char *buf
, size_t size
, size_t nmemb
,
61 size_t len
= size
* nmemb
;
65 char *pingInt_start
= 0;
66 size_t pingInt_off
= 0;
67 struct state
volatile *s
= (struct state
volatile *) userdata
;
69 if (len
/ size
!= nmemb
) {
70 ERROR_MESSAGE("overflow");
74 for (sid_off
= 0; sid_off
+ 7 < len
; ++sid_off
) {
75 if (!strncmp(buf
+ sid_off
, "\"sid\":\"", 7)) {
77 sid_start
= buf
+ sid_off
;
86 for (sid_len
= 0; sid_off
+ sid_len
< len
; ++sid_len
) {
87 if (sid_start
[sid_len
] == '"') {
88 if (!(s
->sid
= strndup(sid_start
, sid_len
))) {
89 PERROR_MESSAGE("strndup");
97 for (pingInt_off
= 0; pingInt_off
+ 15 < len
; ++pingInt_off
) {
98 if (!strncmp(buf
+ pingInt_off
, "\"pingInterval\":", 15)) {
100 pingInt_start
= buf
+ pingInt_off
;
104 if (!pingInt_start
) {
108 s
->ping_interval
= strtoll(pingInt_start
, 0, 10);
116 /* snprintf + malloc + sprintf */
117 int perform_lws_write(struct lws
*wsi
, const char *format
, ...)
124 va_start(arglist
, format
);
125 len
= vsnprintf(0, 0, format
, arglist
);
128 if (!(mem
= malloc(len
+ LWS_PRE
+ 1))) {
132 va_start(arglist
, format
);
133 vsprintf(mem
+ LWS_PRE
, format
, arglist
);
135 lws_write(wsi
, (unsigned char *) (mem
+ LWS_PRE
), len
, LWS_WRITE_TEXT
);
143 /* Follow the breadcrumb trail of servers */
144 static size_t cb_get_real_server(char *buf
, size_t size
, size_t nmemb
,
148 size_t len
= size
* nmemb
;
149 struct real_server_ud
*rsu
= (struct real_server_ud
*) userdata
;
150 size_t old_len
= rsu
->chunk_len
;
154 yajl_val servers
= 0;
155 char eb
[1024] = { 0 };
156 const char *url_path
[] = { "url", 0 };
157 const char *servers_path
[] = { "servers", 0 };
159 if (len
/ size
!= nmemb
) {
160 ERROR_MESSAGE("overflow");
164 if (SSIZE_MAX
- len
- 1 < rsu
->chunk_len
) {
165 ERROR_MESSAGE("overflow");
169 new_len
= rsu
->chunk_len
+ len
;
171 if (!(newmem
= realloc(rsu
->chunk
, new_len
+ 1))) {
172 PERROR_MESSAGE("malloc");
177 rsu
->chunk_len
= new_len
;
178 memcpy(rsu
->chunk
+ old_len
, buf
, len
);
179 rsu
->chunk
[new_len
] = 0;
182 /* See handle_state: we're trying to save some memleaks in yajl */
183 if (rsu
->chunk
[new_len
- 1] != '}') {
187 tree
= yajl_tree_parse(rsu
->chunk
, eb
, 1024);
194 !(servers
= yajl_tree_get(tree
, servers_path
, yajl_t_array
))) {
198 if (!(YAJL_IS_ARRAY(servers
))) {
202 for (size_t k
= 0; k
< YAJL_GET_ARRAY(servers
)->len
; ++k
) {
203 yajl_val e
= YAJL_GET_ARRAY(servers
)->values
[k
];
204 const char *url
= YAJL_GET_STRING(yajl_tree_get(e
, url_path
,
211 if ((rsu
->s
->https
&&
212 !strncmp(url
, "https://", 8)) ||
214 !strncmp(url
, "http://", 7))) {
215 free(rsu
->s
->socket_host
);
216 rsu
->s
->socket_host
= 0;
218 if (!(rsu
->s
->socket_host
= strdup(url
))) {
227 yajl_tree_free(tree
);
232 /* Rip ip-session cookie out of headers */
233 static size_t cb_save_headers(char *buf
, size_t size
, size_t nmemb
,
237 size_t len
= size
* nmemb
;
238 size_t cookie_len
= 0;
239 char *cookie_start
= 0;
244 if (len
/ size
!= nmemb
) {
245 ERROR_MESSAGE("overflow");
249 /* buf had better contain the whole Set-Cookie: stuff */
250 if (!(cookie_start
= strstr(buf
, "Set-Cookie: "))) {
254 cookie_start
+= strlen("Set-Cookie: ");
256 while (cookie_start
[cookie_len
] &&
257 cookie_start
[cookie_len
] != ';') {
261 if (cookie_start
[cookie_len
] != ';') {
262 ERROR_MESSAGE("ip-session cookie unterminated -- bailing");
266 cookie_start
[cookie_len
] = 0;
268 if (!(newmem
= realloc(cookies
, (cookies_sz
+ 1) * sizeof *cookies
))) {
269 PERROR_MESSAGE("realloc");
276 if (!(cookies
[cookies_sz
- 1] = strdup(cookie_start
))) {
277 PERROR_MESSAGE("strdup");
287 /* Extract the ip-session cookie that sync demands */
289 int cytube_get_session_cookie(const char *server
, const char *protocol
, const
290 char *channel
, struct state
volatile *s
)
293 Grumble grumble -- we only need libcurl for one request,
294 to get the ip-session cookie, and we *could* parse that
295 ourselves except for the fact that everything redirects
296 to https and *ssl is a pita.
300 char curl_error
[CURL_ERROR_SIZE
];
302 struct real_server_ud rsu
= { .s
= s
};
306 if (!(ch
= curl_easy_init())) {
307 ERROR_MESSAGE("curl_easy_init failed");
311 /* XXX: THESE ARE NOT NEEDED YOU JUST NEED SID */
312 /* First, grab the _csrf and ip-session cookies */
313 if (!(url
= aprintf("%s://%s/r/%s", protocol
, server
, channel
))) {
314 PERROR_MESSAGE("aprintf");
318 curl_easy_setopt(ch
, CURLOPT_URL
, url
);
319 curl_easy_setopt(ch
, CURLOPT_NOBODY
, 1L);
320 curl_easy_setopt(ch
, CURLOPT_FOLLOWLOCATION
, 2L);
321 curl_easy_setopt(ch
, CURLOPT_MAXREDIRS
, 2L);
322 curl_easy_setopt(ch
, CURLOPT_HEADERFUNCTION
, cb_save_headers
);
323 curl_easy_setopt(ch
, CURLOPT_WRITEFUNCTION
, curl_drop
);
324 curl_easy_setopt(ch
, CURLOPT_ERRORBUFFER
, &curl_error
);
326 if (curl_easy_perform(ch
) != CURLE_OK
) {
327 ERROR_MESSAGE("curl_easy_perform: %s", curl_error
);
331 /* Now, grab the real server */
334 if (!(url
= aprintf("%s://%s/socketconfig/%s.json", protocol
, server
,
336 PERROR_MESSAGE("aprintf");
340 curl_easy_setopt(ch
, CURLOPT_URL
, url
);
341 curl_easy_setopt(ch
, CURLOPT_NOBODY
, 0L);
342 curl_easy_setopt(ch
, CURLOPT_FOLLOWLOCATION
, 2L);
343 curl_easy_setopt(ch
, CURLOPT_MAXREDIRS
, 2L);
344 curl_easy_setopt(ch
, CURLOPT_WRITEDATA
, (void *) &rsu
);
345 curl_easy_setopt(ch
, CURLOPT_WRITEFUNCTION
, cb_get_real_server
);
346 curl_easy_setopt(ch
, CURLOPT_HEADERFUNCTION
, curl_drop
);
347 curl_easy_setopt(ch
, CURLOPT_ERRORBUFFER
, &curl_error
);
349 if (curl_easy_perform(ch
) != CURLE_OK
) {
350 ERROR_MESSAGE("curl_easy_perform: %s", curl_error
);
355 rsu
= (struct real_server_ud
) { 0 };
357 /* Now, connect to the socket.io thing to get the sid */
360 if (!(url
= aprintf("%s/socket.io/?EIO=3&transport=polling",
362 PERROR_MESSAGE("aprintf");
366 curl_easy_setopt(ch
, CURLOPT_URL
, url
);
367 curl_easy_setopt(ch
, CURLOPT_WRITEFUNCTION
, cb_extract_sid_and_ping
);
368 curl_easy_setopt(ch
, CURLOPT_WRITEDATA
, (void *) s
);
370 if (curl_easy_perform(ch
) != CURLE_OK
) {
371 ERROR_MESSAGE("curl_easy_perform: %s", curl_error
);
376 ERROR_MESSAGE("/socket.io did not return sid -- bailing");
380 if (s
->ping_interval
< 200) {
381 s
->ping_interval
= 200;
389 curl_easy_cleanup(ch
);
392 curl_global_cleanup();
397 /* Deal with something to do with lws */
398 int cytube_lws_handler(struct lws
*wsi
, enum lws_callback_reasons reason
,
399 void *user
, void *in
, size_t len
)
401 unsigned char **p
= 0;
402 unsigned char *end
= 0;
405 struct state
volatile *s
= main_s
;
410 ERROR_MESSAGE("cci is somehow not set");
414 ERROR_MESSAGE("s is somehow not set");
418 case LWS_CALLBACK_PROTOCOL_INIT
:
421 "/socket.io/?EIO=3&transport=websocket&sid=%s",
423 ERROR_MESSAGE("aprintf");
429 lws_client_connect_via_info(cci
);
433 case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER
:
434 p
= (unsigned char **) in
;
437 for (size_t k
= 0; k
< cookies_sz
; ++k
) {
438 if (lws_add_http_header_by_token( /* */
439 wsi
, WSI_TOKEN_HTTP_COOKIE
, /* */
440 (unsigned char *) cookies
[k
], /* */
441 strlen(cookies
[k
]), p
, end
)) {
447 case LWS_CALLBACK_CLIENT_ESTABLISHED
:
451 s
->must_write_upgrade
= 1;
452 s
->must_join_channel
= 1;
453 lws_callback_on_writable(wsi
);
455 case LWS_CALLBACK_CLIENT_WRITEABLE
:
457 if (s
->must_write_upgrade
) {
458 /* I have no idea why we have to do this */
459 perform_lws_write(wsi
, "5");
460 s
->must_write_upgrade
= 0;
462 } else if (s
->must_write_ping
) {
463 perform_lws_write(wsi
, "2");
464 s
->must_write_ping
= 0;
466 } else if (s
->must_join_channel
) {
467 perform_lws_write(wsi
,
468 "42[\"joinChannel\", {\"name\": \"%s\"}]",
470 s
->must_join_channel
= 0;
472 /* We have to wait until we've fully joined to ask for the playlist */
474 } else if (s
->must_ask_for_playlist
) {
475 perform_lws_write(wsi
, "42[\"requestPlaylist\"]");
476 s
->must_ask_for_playlist
= 0;
481 case LWS_CALLBACK_CLIENT_RECEIVE
:
482 str
= (const char *) in
;
486 s
->stored_cmd_len
>= (1 << 25)) {
487 /* This is a PONG. Scrap whatever incompletes we've got */
490 s
->stored_cmd_len
= 0;
491 } else if (s
->stored_cmd
) {
492 /* We're continuing an incomplete message, right? */
495 if (!(newmem
= realloc(s
->stored_cmd
,
496 s
->stored_cmd_len
+ len
))) {
499 s
->stored_cmd_len
= 0;
501 s
->stored_cmd
= newmem
;
502 memcpy(s
->stored_cmd
+ s
->stored_cmd_len
, str
,
504 s
->stored_cmd_len
+= len
;
506 if (state_handle(s
, s
->stored_cmd
,
507 s
->stored_cmd_len
) >= 0) {
510 s
->stored_cmd_len
= 0;
513 } else if (len
> 2 &&
517 if (state_handle(s
, str
+ 2, len
- 2) < 0) {
518 /* Perhaps this is the start of an incomplete message */
519 if ((s
->stored_cmd
= strndup(str
+ 2, len
-
521 s
->stored_cmd_len
= len
- 2;
526 /* HANDLE MESSAGES HERE */
528 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR
:
529 ERROR_MESSAGE("LWS error: %*s", (int) len
, in
? (char *) in
:
533 case LWS_CALLBACK_PROTOCOL_DESTROY
:
534 case LWS_CALLBACK_WSI_DESTROY
:
537 case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS
:
538 case LWS_CALLBACK_GET_THREAD_ID
:
539 case LWS_CALLBACK_ADD_POLL_FD
:
540 case LWS_CALLBACK_WSI_CREATE
:
541 case LWS_CALLBACK_EVENT_WAIT_CANCELLED
:
542 case LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION
:
543 case LWS_CALLBACK_LOCK_POLL
:
544 case LWS_CALLBACK_UNLOCK_POLL
:
545 case LWS_CALLBACK_CHANGE_MODE_POLL_FD
:
546 case LWS_CALLBACK_DEL_POLL_FD
:
547 case LWS_CALLBACK_VHOST_CERT_AGING
:
551 /* We get 2 on startup (?) and 75 on destroy (?) */
552 /* ERROR_MESSAGE("unknown lws response; reason = %d", reason); */
559 /* Pass in the cci for use in handler */
560 void cytube_set_cci_and_s(struct lws_client_connect_info
*ccip
, struct state