2 * Copyright (c) 2019, 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
;
35 /* Hurrah, another layer of indirection */
36 struct real_server_ud
{
38 struct state
volatile *s
;
45 curl_drop(char *buf
, size_t size
, size_t nmemb
, void *userdata
)
53 /* Rip sid out of a JSON-esque reply */
55 cb_extract_sid_and_ping(char *buf
, size_t size
, size_t nmemb
, void *userdata
)
58 size_t len
= size
* nmemb
;
62 char *pingInt_start
= 0;
63 size_t pingInt_off
= 0;
64 struct state
volatile *s
= (struct state
volatile *) userdata
;
66 if (len
/ size
!= nmemb
) {
67 ERROR_MESSAGE("overflow");
71 for (sid_off
= 0; sid_off
+ 7 < len
; ++sid_off
) {
72 if (!strncmp(buf
+ sid_off
, "\"sid\":\"", 7)) {
74 sid_start
= buf
+ sid_off
;
83 for (sid_len
= 0; sid_off
+ sid_len
< len
; ++sid_len
) {
84 if (sid_start
[sid_len
] == '"') {
85 if (!(s
->sid
= strndup(sid_start
, sid_len
))) {
86 PERROR_MESSAGE("strndup");
94 for (pingInt_off
= 0; pingInt_off
+ 15 < len
; ++pingInt_off
) {
95 if (!strncmp(buf
+ pingInt_off
, "\"pingInterval\":", 15)) {
97 pingInt_start
= buf
+ pingInt_off
;
101 if (!pingInt_start
) {
105 s
->ping_interval
= strtoll(pingInt_start
, 0, 10);
113 /* snprintf + malloc + sprintf */
115 perform_lws_write(struct lws
*wsi
, const char *format
, ...)
122 va_start(arglist
, format
);
123 len
= vsnprintf(0, 0, format
, arglist
);
126 if (!(mem
= malloc(len
+ LWS_PRE
+ 1))) {
130 va_start(arglist
, format
);
131 vsprintf(mem
+ LWS_PRE
, format
, arglist
);
133 lws_write(wsi
, (unsigned char *) (mem
+ LWS_PRE
), len
, LWS_WRITE_TEXT
);
141 /* Follow the breadcrumb trail of servers */
143 cb_get_real_server(char *buf
, size_t size
, size_t nmemb
, void *userdata
)
146 size_t len
= size
* nmemb
;
147 struct real_server_ud
*rsu
= (struct real_server_ud
*) userdata
;
148 size_t old_len
= rsu
->chunk_len
;
152 yajl_val servers
= 0;
153 char eb
[1024] = { 0 };
154 const char *url_path
[] = { "url", 0 };
155 const char *servers_path
[] = { "servers", 0 };
157 if (len
/ size
!= nmemb
) {
158 ERROR_MESSAGE("overflow");
162 if (SSIZE_MAX
- len
- 1 < rsu
->chunk_len
) {
163 ERROR_MESSAGE("overflow");
167 new_len
= rsu
->chunk_len
+ len
;
169 if (!(newmem
= realloc(rsu
->chunk
, new_len
+ 1))) {
170 PERROR_MESSAGE("malloc");
175 rsu
->chunk_len
= new_len
;
176 memcpy(rsu
->chunk
+ old_len
, buf
, len
);
177 rsu
->chunk
[new_len
] = 0;
180 /* See handle_state: we're trying to save some memleaks in yajl */
181 if (rsu
->chunk
[new_len
- 1] != '}') {
185 tree
= yajl_tree_parse(rsu
->chunk
, eb
, 1024);
192 !(servers
= yajl_tree_get(tree
, servers_path
, yajl_t_array
))) {
196 if (!(YAJL_IS_ARRAY(servers
))) {
200 for (size_t k
= 0; k
< YAJL_GET_ARRAY(servers
)->len
; ++k
) {
201 yajl_val e
= YAJL_GET_ARRAY(servers
)->values
[k
];
202 const char *url
= YAJL_GET_STRING(yajl_tree_get(e
, url_path
,
209 if ((rsu
->s
->https
&&
210 !strncmp(url
, "https://", 8)) ||
212 !strncmp(url
, "http://", 7))) {
213 free(rsu
->s
->socket_host
);
214 rsu
->s
->socket_host
= 0;
216 if (!(rsu
->s
->socket_host
= strdup(url
))) {
225 yajl_tree_free(tree
);
230 /* Extract the session id cookie that sync demands */
233 cytube_get_session_cookie(const char *server
, const char *protocol
, const
234 char *channel
, struct state
volatile *s
)
237 * Grumble grumble -- we only need libcurl for one request,
238 * to get the json file, and we *could* parse that
239 * ourselves except for the fact that everything redirects
240 * to https and *ssl is a pita.
244 char curl_error
[CURL_ERROR_SIZE
];
246 struct real_server_ud rsu
= { .s
= s
};
250 if (!(ch
= curl_easy_init())) {
251 ERROR_MESSAGE("curl_easy_init failed");
255 /* Grab real connection info */
256 if (!(url
= aprintf("%s://%s/socketconfig/%s.json", protocol
, server
,
258 PERROR_MESSAGE("aprintf");
262 curl_easy_setopt(ch
, CURLOPT_URL
, url
);
263 curl_easy_setopt(ch
, CURLOPT_NOBODY
, 0L);
264 curl_easy_setopt(ch
, CURLOPT_FOLLOWLOCATION
, 2L);
265 curl_easy_setopt(ch
, CURLOPT_MAXREDIRS
, 2L);
266 curl_easy_setopt(ch
, CURLOPT_WRITEDATA
, (void *) &rsu
);
267 curl_easy_setopt(ch
, CURLOPT_WRITEFUNCTION
, cb_get_real_server
);
268 curl_easy_setopt(ch
, CURLOPT_HEADERFUNCTION
, curl_drop
);
269 curl_easy_setopt(ch
, CURLOPT_ERRORBUFFER
, &curl_error
);
271 if (curl_easy_perform(ch
) != CURLE_OK
) {
272 ERROR_MESSAGE("curl_easy_perform: %s", curl_error
);
277 rsu
= (struct real_server_ud
) { 0 };
279 /* Now, connect to the socket.io thing to get the sid */
282 if (!(url
= aprintf("%s/socket.io/?EIO=3&transport=polling",
284 PERROR_MESSAGE("aprintf");
288 curl_easy_setopt(ch
, CURLOPT_URL
, url
);
289 curl_easy_setopt(ch
, CURLOPT_WRITEFUNCTION
, cb_extract_sid_and_ping
);
290 curl_easy_setopt(ch
, CURLOPT_WRITEDATA
, (void *) s
);
292 if (curl_easy_perform(ch
) != CURLE_OK
) {
293 ERROR_MESSAGE("curl_easy_perform: %s", curl_error
);
298 ERROR_MESSAGE("/socket.io did not return sid -- bailing");
302 if (s
->ping_interval
< 200) {
303 s
->ping_interval
= 200;
311 curl_easy_cleanup(ch
);
314 curl_global_cleanup();
319 /* Deal with something to do with lws */
321 cytube_lws_handler(struct lws
*wsi
, enum lws_callback_reasons reason
,
322 void *user
, void *in
, size_t len
)
326 struct state
volatile *s
= main_s
;
331 ERROR_MESSAGE("cci is somehow not set");
335 ERROR_MESSAGE("s is somehow not set");
339 case LWS_CALLBACK_PROTOCOL_INIT
:
342 "/socket.io/?EIO=3&transport=websocket&sid=%s",
344 ERROR_MESSAGE("aprintf");
350 lws_client_connect_via_info(cci
);
354 case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER
:
356 case LWS_CALLBACK_CLIENT_ESTABLISHED
:
360 s
->must_write_upgrade
= 1;
361 s
->must_join_channel
= 1;
362 lws_callback_on_writable(wsi
);
364 case LWS_CALLBACK_CLIENT_WRITEABLE
:
366 if (s
->must_write_upgrade
) {
367 /* I have no idea why we have to do this */
368 perform_lws_write(wsi
, "5");
369 s
->must_write_upgrade
= 0;
371 } else if (s
->must_write_ping
) {
372 perform_lws_write(wsi
, "2");
373 s
->must_write_ping
= 0;
375 } else if (s
->must_join_channel
) {
376 perform_lws_write(wsi
,
377 "42[\"joinChannel\", {\"name\": \"%s\"}]",
379 s
->must_join_channel
= 0;
381 /* We have to wait until we've fully joined to ask for the playlist */
383 } else if (s
->must_ask_for_playlist
) {
384 perform_lws_write(wsi
, "42[\"requestPlaylist\"]");
385 s
->must_ask_for_playlist
= 0;
390 case LWS_CALLBACK_CLIENT_RECEIVE
:
391 str
= (const char *) in
;
395 s
->stored_cmd_len
>= (1 << 25)) {
396 /* This is a PONG. Scrap whatever incompletes we've got */
399 s
->stored_cmd_len
= 0;
400 } else if (s
->stored_cmd
) {
401 /* We're continuing an incomplete message, right? */
404 if (!(newmem
= realloc(s
->stored_cmd
,
405 s
->stored_cmd_len
+ len
))) {
408 s
->stored_cmd_len
= 0;
410 s
->stored_cmd
= newmem
;
411 memcpy(s
->stored_cmd
+ s
->stored_cmd_len
, str
,
413 s
->stored_cmd_len
+= len
;
415 if (state_handle(s
, s
->stored_cmd
,
416 s
->stored_cmd_len
) >= 0) {
419 s
->stored_cmd_len
= 0;
422 } else if (len
> 2 &&
426 if (state_handle(s
, str
+ 2, len
- 2) < 0) {
427 /* Perhaps this is the start of an incomplete message */
428 if ((s
->stored_cmd
= strndup(str
+ 2, len
-
430 s
->stored_cmd_len
= len
- 2;
435 /* HANDLE MESSAGES HERE */
437 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR
:
438 ERROR_MESSAGE("LWS error: %*s", (int) len
, in
? (char *) in
:
442 case LWS_CALLBACK_PROTOCOL_DESTROY
:
443 case LWS_CALLBACK_WSI_DESTROY
:
446 case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS
:
447 case LWS_CALLBACK_GET_THREAD_ID
:
448 case LWS_CALLBACK_ADD_POLL_FD
:
449 case LWS_CALLBACK_WSI_CREATE
:
450 case LWS_CALLBACK_EVENT_WAIT_CANCELLED
:
451 case LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION
:
452 case LWS_CALLBACK_LOCK_POLL
:
453 case LWS_CALLBACK_UNLOCK_POLL
:
454 case LWS_CALLBACK_CHANGE_MODE_POLL_FD
:
455 case LWS_CALLBACK_DEL_POLL_FD
:
456 case LWS_CALLBACK_VHOST_CERT_AGING
:
460 /* We get 2 on startup (?) and 75 on destroy (?) */
461 /* ERROR_MESSAGE("unknown lws response; reason = %d", reason); */
468 /* Pass in the cci for use in handler */
470 cytube_set_cci_and_s(struct lws_client_connect_info
*ccip
, struct state