1111#include <inttypes.h>
1212#include <stdlib.h>
1313#include <string.h>
14+ #include <ctype.h>
1415
1516#ifdef CLOUDSYNC_NETWORK_TRACE
1617#ifdef _WIN32
@@ -71,10 +72,13 @@ struct network_data {
7172 char site_id [UUID_STR_MAXLEN ];
7273 char * authentication ; // apikey or token
7374 char * org_id ; // organization ID for X-CloudSync-Org header
75+ char * ticket ; // optional short-lived sync runtime ticket
76+ char * ticket_expires_at ;
7477 char * check_endpoint ;
7578 char * upload_endpoint ;
7679 char * apply_endpoint ;
7780 char * status_endpoint ;
81+ int ticket_enabled ;
7882#ifndef CLOUDSYNC_OMIT_CURL
7983 CURL * api_curl ;
8084 CURL * artifact_curl ;
@@ -163,6 +167,11 @@ typedef struct {
163167 size_t read_pos ;
164168} network_read_data ;
165169
170+ typedef struct {
171+ char * ticket ;
172+ char * expires_at ;
173+ } network_ticket_headers ;
174+
166175static const char * cloudsync_default_headers [] = {
167176 CLOUDSYNC_HEADER_VERSION_LINE ,
168177};
@@ -192,12 +201,25 @@ char *network_data_get_orgid (network_data *data) {
192201 return data -> org_id ;
193202}
194203
204+ char * network_data_get_ticket (network_data * data ) {
205+ return data -> ticket ;
206+ }
207+
208+ static void network_data_clear_ticket (network_data * data ) {
209+ if (!data ) return ;
210+ if (data -> ticket ) cloudsync_memory_free (data -> ticket );
211+ if (data -> ticket_expires_at ) cloudsync_memory_free (data -> ticket_expires_at );
212+ data -> ticket = NULL ;
213+ data -> ticket_expires_at = NULL ;
214+ }
215+
195216bool network_data_set_endpoints (network_data * data , char * auth , char * check , char * upload , char * apply , char * status ) {
196217 // sanity check
197218 if (!check || !upload ) return false;
198219
199220 // always free previous owned pointers
200221 if (data -> authentication ) cloudsync_memory_free (data -> authentication );
222+ network_data_clear_ticket (data );
201223 if (data -> check_endpoint ) cloudsync_memory_free (data -> check_endpoint );
202224 if (data -> upload_endpoint ) cloudsync_memory_free (data -> upload_endpoint );
203225 if (data -> apply_endpoint ) cloudsync_memory_free (data -> apply_endpoint );
@@ -259,6 +281,7 @@ void network_data_free (network_data *data) {
259281#endif
260282 if (data -> authentication ) cloudsync_memory_free (data -> authentication );
261283 if (data -> org_id ) cloudsync_memory_free (data -> org_id );
284+ network_data_clear_ticket (data );
262285 if (data -> check_endpoint ) cloudsync_memory_free (data -> check_endpoint );
263286 if (data -> upload_endpoint ) cloudsync_memory_free (data -> upload_endpoint );
264287 if (data -> apply_endpoint ) cloudsync_memory_free (data -> apply_endpoint );
@@ -268,27 +291,68 @@ void network_data_free (network_data *data) {
268291
269292// MARK: - Utils -
270293
294+ static bool network_endpoint_is_api (network_data * data , const char * endpoint ) {
295+ if (!data || !endpoint ) return false;
296+ return (data -> check_endpoint && strcmp (endpoint , data -> check_endpoint ) == 0 ) ||
297+ (data -> upload_endpoint && strcmp (endpoint , data -> upload_endpoint ) == 0 ) ||
298+ (data -> apply_endpoint && strcmp (endpoint , data -> apply_endpoint ) == 0 ) ||
299+ (data -> status_endpoint && strcmp (endpoint , data -> status_endpoint ) == 0 );
300+ }
301+
302+ static bool network_env_disabled (const char * value ) {
303+ return value && (strcmp (value , "0" ) == 0 || strcmp (value , "false" ) == 0 || strcmp (value , "off" ) == 0 || strcmp (value , "no" ) == 0 );
304+ }
305+
306+ static bool network_ticket_enabled (network_data * data ) {
307+ if (!data ) return false;
308+ if (data -> ticket_enabled == 0 ) {
309+ const char * value = getenv ("CLOUDSYNC_NETWORK_TICKET" );
310+ data -> ticket_enabled = network_env_disabled (value ) ? -1 : 1 ;
311+ }
312+ return data -> ticket_enabled > 0 ;
313+ }
314+
315+ bool network_data_should_use_ticket (network_data * data , const char * endpoint , const char * authentication ) {
316+ return data && authentication && authentication [0 ] != '\0' && data -> ticket && data -> ticket [0 ] != '\0' &&
317+ network_ticket_enabled (data ) && network_endpoint_is_api (data , endpoint );
318+ }
319+
320+ void network_data_update_ticket (network_data * data , const char * ticket , const char * expires_at ) {
321+ if (!data || !ticket || ticket [0 ] == '\0' ) return ;
322+
323+ char * ticket_copy = cloudsync_string_dup (ticket );
324+ if (!ticket_copy ) return ;
325+
326+ char * expires_copy = NULL ;
327+ if (expires_at && expires_at [0 ] != '\0' ) {
328+ expires_copy = cloudsync_string_dup (expires_at );
329+ if (!expires_copy ) {
330+ cloudsync_memory_free (ticket_copy );
331+ return ;
332+ }
333+ }
334+
335+ network_data_clear_ticket (data );
336+ data -> ticket = ticket_copy ;
337+ data -> ticket_expires_at = expires_copy ;
338+
339+ #ifdef CLOUDSYNC_NETWORK_TRACE
340+ fprintf (stderr ,
341+ "[cloudsync-network] received_ticket=%s expires_at=%s\n" ,
342+ data -> ticket , data -> ticket_expires_at ? data -> ticket_expires_at : "" );
343+ #endif
344+ }
345+
271346#ifndef CLOUDSYNC_OMIT_CURL
272347static bool network_curl_pool_enabled (network_data * data ) {
273348 if (!data ) return false;
274349 if (data -> curl_pool_enabled == 0 ) {
275350 const char * value = getenv ("CLOUDSYNC_CURL_POOL" );
276- data -> curl_pool_enabled = 1 ;
277- if (value && (strcmp (value , "0" ) == 0 || strcmp (value , "false" ) == 0 || strcmp (value , "off" ) == 0 || strcmp (value , "no" ) == 0 )) {
278- data -> curl_pool_enabled = -1 ;
279- }
351+ data -> curl_pool_enabled = network_env_disabled (value ) ? -1 : 1 ;
280352 }
281353 return data -> curl_pool_enabled > 0 ;
282354}
283355
284- static bool network_endpoint_is_api (network_data * data , const char * endpoint ) {
285- if (!data || !endpoint ) return false;
286- return (data -> check_endpoint && strcmp (endpoint , data -> check_endpoint ) == 0 ) ||
287- (data -> upload_endpoint && strcmp (endpoint , data -> upload_endpoint ) == 0 ) ||
288- (data -> apply_endpoint && strcmp (endpoint , data -> apply_endpoint ) == 0 ) ||
289- (data -> status_endpoint && strcmp (endpoint , data -> status_endpoint ) == 0 );
290- }
291-
292356static CURL * network_curl_for_endpoint (network_data * data , const char * endpoint , bool * pooled ) {
293357 if (pooled ) * pooled = false;
294358 if (!network_curl_pool_enabled (data )) {
@@ -340,14 +404,65 @@ static size_t network_receive_callback (void *ptr, size_t size, size_t nmemb, vo
340404 return (size * nmemb );
341405}
342406
407+ static bool network_header_eq (const char * line , size_t len , const char * name ) {
408+ size_t name_len = strlen (name );
409+ if (len <= name_len || line [name_len ] != ':' ) return false;
410+ for (size_t i = 0 ; i < name_len ; i ++ ) {
411+ if (tolower ((unsigned char )line [i ]) != tolower ((unsigned char )name [i ])) return false;
412+ }
413+ return true;
414+ }
415+
416+ static char * network_header_value_dup (const char * line , size_t len , const char * name ) {
417+ size_t name_len = strlen (name );
418+ const char * start = line + name_len + 1 ;
419+ const char * end = line + len ;
420+
421+ while (start < end && (* start == ' ' || * start == '\t' )) start ++ ;
422+ while (end > start && (end [-1 ] == '\r' || end [-1 ] == '\n' || end [-1 ] == ' ' || end [-1 ] == '\t' )) end -- ;
423+
424+ size_t value_len = (size_t )(end - start );
425+ char * value = cloudsync_memory_zeroalloc (value_len + 1 );
426+ if (!value ) return NULL ;
427+ memcpy (value , start , value_len );
428+ value [value_len ] = '\0' ;
429+ return value ;
430+ }
431+
432+ static size_t network_header_callback (char * buffer , size_t size , size_t nitems , void * userdata ) {
433+ network_ticket_headers * ticket_headers = (network_ticket_headers * )userdata ;
434+ size_t len = size * nitems ;
435+
436+ if (network_header_eq (buffer , len , CLOUDSYNC_HEADER_TICKET )) {
437+ char * ticket = network_header_value_dup (buffer , len , CLOUDSYNC_HEADER_TICKET );
438+ if (ticket ) {
439+ if (ticket_headers -> ticket ) cloudsync_memory_free (ticket_headers -> ticket );
440+ ticket_headers -> ticket = ticket ;
441+ }
442+ } else if (network_header_eq (buffer , len , CLOUDSYNC_HEADER_TICKET_EXPIRES_AT )) {
443+ char * expires_at = network_header_value_dup (buffer , len , CLOUDSYNC_HEADER_TICKET_EXPIRES_AT );
444+ if (expires_at ) {
445+ if (ticket_headers -> expires_at ) cloudsync_memory_free (ticket_headers -> expires_at );
446+ ticket_headers -> expires_at = expires_at ;
447+ }
448+ }
449+
450+ return len ;
451+ }
452+
343453NETWORK_RESULT network_receive_buffer (network_data * data , const char * endpoint , const char * authentication , bool zero_terminated , bool is_post_request , char * json_payload , const char * * extra_headers , int nextra_headers ) {
344454 char * buffer = NULL ;
345455 size_t blen = 0 ;
346456 struct curl_slist * headers = NULL ;
457+ network_ticket_headers ticket_headers = {NULL , NULL };
347458 char errbuf [CURL_ERROR_SIZE ] = {0 };
348459 long response_code = 0 ;
349460 bool pooled = false;
461+ bool using_ticket = network_data_should_use_ticket (data , endpoint , authentication );
350462 const char * method = (json_payload || is_post_request ) ? "POST" : "GET" ;
463+ #ifndef CLOUDSYNC_NETWORK_TRACE
464+ (void )method ;
465+ #endif
351466#ifdef CLOUDSYNC_NETWORK_TRACE
352467 double trace_start_ms = network_trace_now_ms ();
353468#endif
@@ -397,12 +512,21 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
397512 if (!tmp ) {rc = CURLE_OUT_OF_MEMORY ; goto cleanup ;}
398513 headers = tmp ;
399514 }
515+ if (using_ticket ) {
516+ char ticket_header [CLOUDSYNC_SESSION_TOKEN_MAXSIZE ];
517+ snprintf (ticket_header , sizeof (ticket_header ), "%s: %s" , CLOUDSYNC_HEADER_TICKET , data -> ticket );
518+ struct curl_slist * tmp = curl_slist_append (headers , ticket_header );
519+ if (!tmp ) {rc = CURLE_OUT_OF_MEMORY ; goto cleanup ;}
520+ headers = tmp ;
521+ }
400522
401523 if (headers ) curl_easy_setopt (curl , CURLOPT_HTTPHEADER , headers );
402524
403525 network_buffer netdata = {NULL , 0 , 0 , (zero_terminated ) ? 1 : 0 };
404526 curl_easy_setopt (curl , CURLOPT_WRITEDATA , & netdata );
405527 curl_easy_setopt (curl , CURLOPT_WRITEFUNCTION , network_receive_callback );
528+ curl_easy_setopt (curl , CURLOPT_HEADERDATA , & ticket_headers );
529+ curl_easy_setopt (curl , CURLOPT_HEADERFUNCTION , network_header_callback );
406530
407531 // add optional JSON payload (implies setting CURLOPT_POST to 1)
408532 // or set the CURLOPT_POST option
@@ -419,13 +543,18 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
419543 if (rc == CURLE_OK ) {
420544 buffer = netdata .buffer ;
421545 blen = netdata .bused ;
546+ if (response_code < 400 && ticket_headers .ticket ) {
547+ network_data_update_ticket (data , ticket_headers .ticket , ticket_headers .expires_at );
548+ }
422549 } else if (netdata .buffer ) {
423550 cloudsync_memory_free (netdata .buffer );
424551 netdata .buffer = NULL ;
425552 }
426553
427554cleanup :
428555 if (headers ) curl_slist_free_all (headers );
556+ if (ticket_headers .ticket ) cloudsync_memory_free (ticket_headers .ticket );
557+ if (ticket_headers .expires_at ) cloudsync_memory_free (ticket_headers .expires_at );
429558
430559 // build result
431560 NETWORK_RESULT result = {0 , NULL , 0 , NULL , NULL };
@@ -440,6 +569,10 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
440569 }
441570
442571 #ifdef CLOUDSYNC_NETWORK_TRACE
572+ fprintf (stderr ,
573+ "[cloudsync-network] endpoint=%s using_ticket=%s\n" ,
574+ network_trace_endpoint_name (data , endpoint ),
575+ using_ticket ? "true" : "false" );
443576 network_trace_log_curl (data , method , endpoint , response_code , result .code , result .blen , curl , pooled , network_trace_now_ms () - trace_start_ms );
444577 #endif
445578 if (curl && !pooled ) curl_easy_cleanup (curl );
@@ -886,6 +1019,8 @@ static bool network_compute_endpoints_with_address (sqlite3_context *context, ne
8861019 snprintf (status_endpoint , requested , "%s/%s/%s/%s/%s" ,
8871020 address , CLOUDSYNC_ENDPOINT_PREFIX , managedDatabaseId , data -> site_id , CLOUDSYNC_ENDPOINT_STATUS );
8881021
1022+ network_data_clear_ticket (data );
1023+
8891024 if (data -> check_endpoint ) cloudsync_memory_free (data -> check_endpoint );
8901025 data -> check_endpoint = check_endpoint ;
8911026
@@ -1001,6 +1136,7 @@ bool cloudsync_network_set_authentication_token (sqlite3_context *context, const
10011136 if (!new_auth_token ) return false;
10021137
10031138 if (data -> authentication ) cloudsync_memory_free (data -> authentication );
1139+ network_data_clear_ticket (data );
10041140 data -> authentication = new_auth_token ;
10051141
10061142 return true;
0 commit comments