@@ -51,6 +51,7 @@ SQLITE_EXTENSION_INIT3
5151struct network_data {
5252 char site_id [UUID_STR_MAXLEN ];
5353 char * authentication ; // apikey or token
54+ char * org_id ; // organization ID for X-CloudSync-Org header
5455 char * check_endpoint ;
5556 char * upload_endpoint ;
5657 char * apply_endpoint ;
@@ -85,6 +86,10 @@ char *network_data_get_siteid (network_data *data) {
8586 return data -> site_id ;
8687}
8788
89+ char * network_data_get_orgid (network_data * data ) {
90+ return data -> org_id ;
91+ }
92+
8893bool network_data_set_endpoints (network_data * data , char * auth , char * check , char * upload , char * apply , char * status ) {
8994 // sanity check
9095 if (!check || !upload ) return false;
@@ -145,8 +150,9 @@ bool network_data_set_endpoints (network_data *data, char *auth, char *check, ch
145150
146151void network_data_free (network_data * data ) {
147152 if (!data ) return ;
148-
153+
149154 if (data -> authentication ) cloudsync_memory_free (data -> authentication );
155+ if (data -> org_id ) cloudsync_memory_free (data -> org_id );
150156 if (data -> check_endpoint ) cloudsync_memory_free (data -> check_endpoint );
151157 if (data -> upload_endpoint ) cloudsync_memory_free (data -> upload_endpoint );
152158 if (data -> apply_endpoint ) cloudsync_memory_free (data -> apply_endpoint );
@@ -219,6 +225,14 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
219225 headers = tmp ;
220226 }
221227
228+ if (data -> org_id ) {
229+ char org_header [512 ];
230+ snprintf (org_header , sizeof (org_header ), "%s: %s" , CLOUDSYNC_HEADER_ORG , data -> org_id );
231+ struct curl_slist * tmp = curl_slist_append (headers , org_header );
232+ if (!tmp ) {rc = CURLE_OUT_OF_MEMORY ; goto cleanup ;}
233+ headers = tmp ;
234+ }
235+
222236 if (json_payload ) {
223237 struct curl_slist * tmp = curl_slist_append (headers , "Content-Type: application/json" );
224238 if (!tmp ) {rc = CURLE_OUT_OF_MEMORY ; goto cleanup ;}
@@ -331,7 +345,15 @@ bool network_send_buffer (network_data *data, const char *endpoint, const char *
331345 if (!tmp ) {rc = CURLE_OUT_OF_MEMORY ; goto cleanup ;}
332346 headers = tmp ;
333347 }
334-
348+
349+ if (data -> org_id ) {
350+ char org_header [512 ];
351+ snprintf (org_header , sizeof (org_header ), "%s: %s" , CLOUDSYNC_HEADER_ORG , data -> org_id );
352+ struct curl_slist * tmp = curl_slist_append (headers , org_header );
353+ if (!tmp ) {rc = CURLE_OUT_OF_MEMORY ; goto cleanup ;}
354+ headers = tmp ;
355+ }
356+
335357 // Set headers if needed (S3 pre-signed URLs usually do not require additional headers)
336358 tmp = curl_slist_append (headers , "Content-Type: application/octet-stream" );
337359 if (!tmp ) {rc = CURLE_OUT_OF_MEMORY ; goto cleanup ;}
@@ -578,144 +600,95 @@ int network_extract_query_param (const char *query, const char *key, char *outpu
578600 return -3 ; // Key not found
579601}
580602
581- #if !defined(CLOUDSYNC_OMIT_CURL ) || defined(SQLITE_WASM_EXTRA_INIT )
582603bool network_compute_endpoints (sqlite3_context * context , network_data * data , const char * conn_string ) {
583- // compute endpoints
604+ // JSON format: {"address":"https://host:port","database":"db.sqlite","projectID":"abc","organizationID":"org","apikey":"KEY"}
584605 bool result = false;
585-
586- char * scheme = NULL ;
587- char * host = NULL ;
588- char * port = NULL ;
589- char * database = NULL ;
590- char * query = NULL ;
591-
606+ size_t conn_len = strlen (conn_string );
607+
608+ char * address = json_extract_string (conn_string , conn_len , "address" );
609+ char * database = json_extract_string (conn_string , conn_len , "database" );
610+ char * project_id = json_extract_string (conn_string , conn_len , "projectID" );
611+ char * org_id = json_extract_string (conn_string , conn_len , "organizationID" );
612+ char * apikey = json_extract_string (conn_string , conn_len , "apikey" );
613+ char * token = json_extract_string (conn_string , conn_len , "token" );
614+
592615 char * authentication = NULL ;
593616 char * check_endpoint = NULL ;
594617 char * upload_endpoint = NULL ;
595618 char * apply_endpoint = NULL ;
596619 char * status_endpoint = NULL ;
597620
598- char * conn_string_https = NULL ;
599-
600- #ifndef SQLITE_WASM_EXTRA_INIT
601- CURLUcode rc = CURLUE_OUT_OF_MEMORY ;
602- CURLU * url = curl_url ();
603- if (!url ) goto finalize ;
604- #endif
605-
606- conn_string_https = cloudsync_string_replace_prefix (conn_string , "sqlitecloud://" , "https://" );
607- if (!conn_string_https ) goto finalize ;
608-
609- #ifndef SQLITE_WASM_EXTRA_INIT
610- // set URL: https://UUID.g5.sqlite.cloud:443/chinook.sqlite?apikey=hWDanFolRT9WDK0p54lufNrIyfgLZgtMw6tb6fbPmpo
611- rc = curl_url_set (url , CURLUPART_URL , conn_string_https , 0 );
612- if (rc != CURLUE_OK ) goto finalize ;
613-
614- // https (MANDATORY)
615- rc = curl_url_get (url , CURLUPART_SCHEME , & scheme , 0 );
616- if (rc != CURLUE_OK ) goto finalize ;
617-
618- // UUID.g5.sqlite.cloud (MANDATORY)
619- rc = curl_url_get (url , CURLUPART_HOST , & host , 0 );
620- if (rc != CURLUE_OK ) goto finalize ;
621-
622- // 443 (OPTIONAL)
623- rc = curl_url_get (url , CURLUPART_PORT , & port , 0 );
624- if (rc != CURLUE_OK && rc != CURLUE_NO_PORT ) goto finalize ;
625- char * port_or_default = port && strcmp (port , "8860" ) != 0 ? port : CLOUDSYNC_DEFAULT_ENDPOINT_PORT ;
626-
627- // /chinook.sqlite (MANDATORY)
628- rc = curl_url_get (url , CURLUPART_PATH , & database , 0 );
629- if (rc != CURLUE_OK ) goto finalize ;
630-
631- // apikey=hWDanFolRT9WDK0p54lufNrIyfgLZgtMw6tb6fbPmpo (OPTIONAL)
632- rc = curl_url_get (url , CURLUPART_QUERY , & query , 0 );
633- if (rc != CURLUE_OK && rc != CURLUE_NO_QUERY ) goto finalize ;
634- #else
635- // Parse: scheme://host[:port]/path?query
636- const char * p = strstr (conn_string_https , "://" );
637- if (!p ) goto finalize ;
638- scheme = substr (conn_string_https , p );
639- p += 3 ;
640- const char * host_start = p ;
641- const char * host_end = strpbrk (host_start , ":/?" );
642- if (!host_end ) goto finalize ;
643- host = substr (host_start , host_end );
644- p = host_end ;
645- if (* p == ':' ) {
646- ++ p ;
647- const char * port_end = strpbrk (p , "/?" );
648- if (!port_end ) goto finalize ;
649- port = substr (p , port_end );
650- p = port_end ;
651- }
652- if (* p == '/' ) {
653- const char * path_start = p ;
654- const char * path_end = strchr (path_start , '?' );
655- if (!path_end ) path_end = path_start + strlen (path_start );
656- database = substr (path_start , path_end );
657- p = path_end ;
621+ // validate mandatory fields
622+ if (!address || !database || !project_id || !org_id ) {
623+ sqlite3_result_error (context , "JSON must contain address, database, projectID, and organizationID" , -1 );
624+ sqlite3_result_error_code (context , SQLITE_ERROR );
625+ goto finalize ;
658626 }
659- if (* p == '?' ) {
660- query = strdup (p );
627+
628+ // parse address: scheme://host[:port]
629+ const char * scheme_end = strstr (address , "://" );
630+ if (!scheme_end ) {
631+ sqlite3_result_error (context , "address must include scheme (e.g. https://host:port)" , -1 );
632+ sqlite3_result_error_code (context , SQLITE_ERROR );
633+ goto finalize ;
661634 }
662- if (! scheme || ! host || ! database ) goto finalize ;
663- char * port_or_default = port && strcmp ( port , "8860" ) != 0 ? port : CLOUDSYNC_DEFAULT_ENDPOINT_PORT ;
664- #endif
665-
666- if ( query != NULL ) {
667- char value [ CLOUDSYNC_SESSION_TOKEN_MAXSIZE ] ;
668- if (! authentication && network_extract_query_param ( query , "apikey" , value , sizeof ( value )) == 0 ) {
669- authentication = network_authentication_token ( " apikey" , value );
670- }
671- if (! authentication && network_extract_query_param ( query , "token " , value , sizeof ( value )) == 0 ) {
672- authentication = network_authentication_token ( " token" , value );
673- }
635+
636+ size_t scheme_len = scheme_end - address ;
637+ const char * host_start = scheme_end + 3 ;
638+ const char * port_sep = strchr ( host_start , ':' );
639+ const char * host_end = port_sep ? port_sep : host_start + strlen ( host_start );
640+ const char * port_str = port_sep ? port_sep + 1 : CLOUDSYNC_DEFAULT_ENDPOINT_PORT ;
641+
642+ // build authentication from apikey or token
643+ if ( apikey ) {
644+ authentication = network_authentication_token ( "apikey " , apikey );
645+ } else if ( token ) {
646+ authentication = network_authentication_token ( "token" , token );
674647 }
675-
676- size_t requested = strlen (scheme ) + strlen (host ) + strlen (port_or_default ) + strlen (CLOUDSYNC_ENDPOINT_PREFIX ) + strlen (database ) + 64 ;
648+
649+ // build endpoints: {scheme}://{host}:{port}/v2/cloudsync/{projectID}/{database}/{siteId}/{action}
650+ size_t requested = scheme_len + 3 + (host_end - host_start ) + 1 + strlen (port_str ) + 1
651+ + strlen (CLOUDSYNC_ENDPOINT_PREFIX ) + 1 + strlen (project_id ) + 1
652+ + strlen (database ) + 1 + UUID_STR_MAXLEN + 1 + 16 ;
677653 check_endpoint = (char * )cloudsync_memory_zeroalloc (requested );
678654 upload_endpoint = (char * )cloudsync_memory_zeroalloc (requested );
679655 apply_endpoint = (char * )cloudsync_memory_zeroalloc (requested );
680656 status_endpoint = (char * )cloudsync_memory_zeroalloc (requested );
681657
682- if ((!upload_endpoint ) || (!check_endpoint ) || (!apply_endpoint ) || (!status_endpoint )) goto finalize ;
658+ if (!check_endpoint || !upload_endpoint || !apply_endpoint || !status_endpoint ) {
659+ sqlite3_result_error_code (context , SQLITE_NOMEM );
660+ goto finalize ;
661+ }
683662
684- snprintf (check_endpoint , requested , "%s://%s:%s/%s%s/%s/%s" , scheme , host , port_or_default , CLOUDSYNC_ENDPOINT_PREFIX , database , data -> site_id , CLOUDSYNC_ENDPOINT_CHECK );
685- snprintf (upload_endpoint , requested , "%s://%s:%s/%s%s/%s/%s" , scheme , host , port_or_default , CLOUDSYNC_ENDPOINT_PREFIX , database , data -> site_id , CLOUDSYNC_ENDPOINT_UPLOAD );
686- snprintf (apply_endpoint , requested , "%s://%s:%s/%s%s/%s/%s" , scheme , host , port_or_default , CLOUDSYNC_ENDPOINT_PREFIX , database , data -> site_id , CLOUDSYNC_ENDPOINT_APPLY );
687- snprintf (status_endpoint , requested , "%s://%s:%s/%s%s/%s/%s" , scheme , host , port_or_default , CLOUDSYNC_ENDPOINT_PREFIX , database , data -> site_id , CLOUDSYNC_ENDPOINT_STATUS );
663+ // format: scheme://host:port/v2/cloudsync/projectID/database/siteId/action
664+ snprintf (check_endpoint , requested , "%.*s://%.*s:%s/%s/%s/%s/%s/%s" ,
665+ (int )scheme_len , address , (int )(host_end - host_start ), host_start , port_str ,
666+ CLOUDSYNC_ENDPOINT_PREFIX , project_id , database , data -> site_id , CLOUDSYNC_ENDPOINT_CHECK );
667+ snprintf (upload_endpoint , requested , "%.*s://%.*s:%s/%s/%s/%s/%s/%s" ,
668+ (int )scheme_len , address , (int )(host_end - host_start ), host_start , port_str ,
669+ CLOUDSYNC_ENDPOINT_PREFIX , project_id , database , data -> site_id , CLOUDSYNC_ENDPOINT_UPLOAD );
670+ snprintf (apply_endpoint , requested , "%.*s://%.*s:%s/%s/%s/%s/%s/%s" ,
671+ (int )scheme_len , address , (int )(host_end - host_start ), host_start , port_str ,
672+ CLOUDSYNC_ENDPOINT_PREFIX , project_id , database , data -> site_id , CLOUDSYNC_ENDPOINT_APPLY );
673+ snprintf (status_endpoint , requested , "%.*s://%.*s:%s/%s/%s/%s/%s/%s" ,
674+ (int )scheme_len , address , (int )(host_end - host_start ), host_start , port_str ,
675+ CLOUDSYNC_ENDPOINT_PREFIX , project_id , database , data -> site_id , CLOUDSYNC_ENDPOINT_STATUS );
688676
689677 result = true;
690-
678+
691679finalize :
692- if (result == false) {
693- // store proper result code/message
694- #ifndef SQLITE_WASM_EXTRA_INIT
695- if (rc != CURLUE_OK ) sqlite3_result_error (context , curl_url_strerror (rc ), -1 );
696- sqlite3_result_error_code (context , (rc != CURLUE_OK ) ? SQLITE_ERROR : SQLITE_NOMEM );
697- #else
698- sqlite3_result_error (context , "URL parse error" , -1 );
699- sqlite3_result_error_code (context , SQLITE_ERROR );
700- #endif
701-
702- // cleanup memory managed by the extension
703- if (authentication ) cloudsync_memory_free (authentication );
704- if (check_endpoint ) cloudsync_memory_free (check_endpoint );
705- if (upload_endpoint ) cloudsync_memory_free (upload_endpoint );
706- if (apply_endpoint ) cloudsync_memory_free (apply_endpoint );
707- if (status_endpoint ) cloudsync_memory_free (status_endpoint );
708- }
709-
710680 if (result ) {
711681 if (authentication ) {
712682 if (data -> authentication ) cloudsync_memory_free (data -> authentication );
713683 data -> authentication = authentication ;
714684 }
715-
685+
686+ if (data -> org_id ) cloudsync_memory_free (data -> org_id );
687+ data -> org_id = cloudsync_string_dup (org_id );
688+
716689 if (data -> check_endpoint ) cloudsync_memory_free (data -> check_endpoint );
717690 data -> check_endpoint = check_endpoint ;
718-
691+
719692 if (data -> upload_endpoint ) cloudsync_memory_free (data -> upload_endpoint );
720693 data -> upload_endpoint = upload_endpoint ;
721694
@@ -724,22 +697,24 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
724697
725698 if (data -> status_endpoint ) cloudsync_memory_free (data -> status_endpoint );
726699 data -> status_endpoint = status_endpoint ;
700+ } else {
701+ if (authentication ) cloudsync_memory_free (authentication );
702+ if (check_endpoint ) cloudsync_memory_free (check_endpoint );
703+ if (upload_endpoint ) cloudsync_memory_free (upload_endpoint );
704+ if (apply_endpoint ) cloudsync_memory_free (apply_endpoint );
705+ if (status_endpoint ) cloudsync_memory_free (status_endpoint );
727706 }
728-
729- // cleanup memory
730- #ifndef SQLITE_WASM_EXTRA_INIT
731- if (url ) curl_url_cleanup (url );
732- #endif
733- if (scheme ) curl_free (scheme );
734- if (host ) curl_free (host );
735- if (port ) curl_free (port );
736- if (database ) curl_free (database );
737- if (query ) curl_free (query );
738- if (conn_string_https && conn_string_https != conn_string ) cloudsync_memory_free (conn_string_https );
739-
707+
708+ // cleanup JSON-extracted strings
709+ if (address ) cloudsync_memory_free (address );
710+ if (database ) cloudsync_memory_free (database );
711+ if (project_id ) cloudsync_memory_free (project_id );
712+ if (org_id ) cloudsync_memory_free (org_id );
713+ if (apikey ) cloudsync_memory_free (apikey );
714+ if (token ) cloudsync_memory_free (token );
715+
740716 return result ;
741717}
742- #endif
743718
744719void network_result_to_sqlite_error (sqlite3_context * context , NETWORK_RESULT res , const char * default_error_message ) {
745720 sqlite3_result_error (context , ((res .code == CLOUDSYNC_NETWORK_ERROR ) && (res .buffer )) ? res .buffer : default_error_message , -1 );
@@ -778,10 +753,9 @@ void cloudsync_network_init (sqlite3_context *context, int argc, sqlite3_value *
778753 // save site_id string representation: 01957493c6c07e14803727e969f1d2cc
779754 cloudsync_uuid_v7_stringify (site_id , netdata -> site_id , false);
780755
781- // connection string is something like:
782- // https://UUID.g5.sqlite.cloud:443/chinook.sqlite?apikey=hWDanFolRT9WDK0p54lufNrIyfgLZgtMw6tb6fbPmpo
783- // or https://UUID.g5.sqlite.cloud:443/chinook.sqlite
784- // apikey part is optional and can be replaced by a session token once client is authenticated
756+ // connection string is a JSON object:
757+ // {"address":"https://UUID.sqlite.cloud:443","database":"chinook.sqlite","projectID":"abc123","organizationID":"org456","apikey":"KEY"}
758+ // apikey/token are optional and can be set later via cloudsync_network_set_token/cloudsync_network_set_apikey
785759
786760 const char * connection_param = (const char * )sqlite3_value_text (argv [0 ]);
787761
0 commit comments