1414#include "cloudsync_private.h"
1515#include "netword_private.h"
1616
17+ #ifndef SQLITE_WASM_EXTRA_INIT
1718#ifndef CLOUDSYNC_OMIT_CURL
1819#include "curl/curl.h"
1920#endif
21+ #else
22+ #include <stdlib.h>
23+ #include <emscripten/fetch.h>
24+ #include <emscripten/emscripten.h>
25+ #endif
2026
2127#ifdef __ANDROID__
2228#include "cacert.h"
@@ -127,6 +133,98 @@ static size_t network_receive_callback (void *ptr, size_t size, size_t nmemb, vo
127133 return (size * nmemb );
128134}
129135
136+ #ifdef SQLITE_WASM_EXTRA_INIT
137+ NETWORK_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 * custom_header ) {
138+
139+ emscripten_fetch_attr_t attr ;
140+ emscripten_fetch_attr_init (& attr );
141+
142+ // Set method
143+ if (json_payload || is_post_request ) {
144+ strcpy (attr .requestMethod , "POST" );
145+ } else {
146+ strcpy (attr .requestMethod , "GET" );
147+ }
148+ attr .attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS ;
149+
150+ // Prepare header array (alternating key, value, NULL-terminated)
151+ const char * headers [11 ];
152+ int h = 0 ;
153+
154+ // Custom header (must be "Key: Value", split at ':')
155+ char * custom_key = NULL ;
156+ if (custom_header ) {
157+ const char * colon = strchr (custom_header , ':' );
158+ if (colon ) {
159+ size_t klen = colon - custom_header ;
160+ custom_key = (char * )malloc (klen + 1 );
161+ strncpy (custom_key , custom_header , klen );
162+ custom_key [klen ] = 0 ;
163+ const char * custom_val = colon + 1 ;
164+ while (* custom_val == ' ' ) custom_val ++ ;
165+ headers [h ++ ] = custom_key ;
166+ headers [h ++ ] = custom_val ;
167+ }
168+ }
169+
170+ // Authorization
171+ char auth_header [256 ];
172+ if (authentication ) {
173+ snprintf (auth_header , sizeof (auth_header ), "Bearer %s" , authentication );
174+ headers [h ++ ] = "Authorization" ;
175+ headers [h ++ ] = auth_header ;
176+ }
177+
178+ // Content-Type for JSON
179+ if (json_payload ) {
180+ headers [h ++ ] = "Content-Type" ;
181+ headers [h ++ ] = "application/json" ;
182+ }
183+
184+ // Accept (always)
185+ headers [h ++ ] = "Accept" ;
186+ headers [h ++ ] = "application/octet-stream" ;
187+ headers [h ] = 0 ;
188+ attr .requestHeaders = headers ;
189+
190+ // Body
191+ if (json_payload ) {
192+ attr .requestData = json_payload ;
193+ attr .requestDataSize = strlen (json_payload );
194+ } else if (is_post_request ) {
195+ attr .requestData = "" ;
196+ attr .requestDataSize = 0 ;
197+ }
198+
199+ emscripten_fetch_t * fetch = emscripten_fetch (& attr , endpoint ); // Blocks here until the operation is complete.
200+ NETWORK_RESULT result = {0 , NULL , 0 , NULL , NULL };
201+ if (fetch -> status == 200 ) {
202+ result .code = (fetch -> numBytes > 0 ) ? CLOUDSYNC_NETWORK_BUFFER : CLOUDSYNC_NETWORK_OK ;
203+ result .buffer = (char * )malloc (fetch -> numBytes + 1 );
204+ if (result .buffer && fetch -> numBytes > 0 ) {
205+ memcpy (result .buffer , fetch -> data , fetch -> numBytes );
206+ result .buffer [fetch -> numBytes ] = 0 ;
207+ result .blen = fetch -> numBytes ;
208+ } else if (result .buffer ) {
209+ result .buffer [0 ] = 0 ;
210+ result .blen = 0 ;
211+ }
212+ } else {
213+ result .code = CLOUDSYNC_NETWORK_ERROR ;
214+ if (fetch -> statusText && fetch -> statusText [0 ])
215+ result .buffer = strdup (fetch -> statusText );
216+ else
217+ result .buffer = strdup ("Network error" );
218+ result .blen = 0 ;
219+ }
220+
221+ // cleanup
222+ emscripten_fetch_close (fetch );
223+ if (custom_key ) free (custom_key );
224+
225+ return result ;
226+ }
227+ #else
130228NETWORK_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 * custom_header ) {
131229 char * buffer = NULL ;
132230 size_t blen = 0 ;
@@ -205,6 +303,7 @@ NETWORK_RESULT network_receive_buffer (network_data *data, const char *endpoint,
205303
206304 return result ;
207305}
306+ #endif
208307
209308static size_t network_read_callback (char * buffer , size_t size , size_t nitems , void * userdata ) {
210309 network_read_data * rd = (network_read_data * )userdata ;
@@ -220,7 +319,45 @@ static size_t network_read_callback(char *buffer, size_t size, size_t nitems, vo
220319 return to_copy ;
221320}
222321
223- bool network_send_buffer (network_data * data , const char * endpoint , const char * authentication , const void * blob , int blob_size ) {
322+ #ifdef SQLITE_WASM_EXTRA_INIT
323+ bool network_send_buffer (network_data * data , const char * endpoint , const char * authentication , const void * blob , int blob_size ) {
324+
325+ bool result = false;
326+ emscripten_fetch_attr_t attr ;
327+ emscripten_fetch_attr_init (& attr );
328+ strcpy (attr .requestMethod , "PUT" );
329+ attr .attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS ;
330+
331+ // Prepare headers (alternating key, value, NULL-terminated)
332+ // Max 3 headers: Accept, (optional Auth), Content-Type
333+ const char * headers [7 ];
334+ int h = 0 ;
335+ headers [h ++ ] = "Accept" ;
336+ headers [h ++ ] = "text/plain" ;
337+ char auth_header [256 ];
338+ if (authentication ) {
339+ snprintf (auth_header , sizeof (auth_header ), "Bearer %s" , authentication );
340+ headers [h ++ ] = "Authorization" ;
341+ headers [h ++ ] = auth_header ;
342+ }
343+ headers [h ++ ] = "Content-Type" ;
344+ headers [h ++ ] = "application/octet-stream" ;
345+ headers [h ] = 0 ;
346+ attr .requestHeaders = headers ;
347+
348+ // Set request body
349+ attr .requestData = (const char * )blob ;
350+ attr .requestDataSize = blob_size ;
351+
352+ emscripten_fetch_t * fetch = emscripten_fetch (& attr , endpoint ); // Blocks here until the operation is complete.
353+ if (fetch -> status == 200 ) result = true;
354+
355+ emscripten_fetch_close (fetch );
356+
357+ return result ;
358+ }
359+ #else
360+ bool network_send_buffer (network_data * data , const char * endpoint , const char * authentication , const void * blob , int blob_size ) {
224361 struct curl_slist * headers = NULL ;
225362 curl_mime * mime = NULL ;
226363 bool result = false;
@@ -292,6 +429,7 @@ bool network_send_buffer (network_data *data, const char *endpoint, const char *
292429 return result ;
293430}
294431#endif
432+ #endif
295433
296434int network_set_sqlite_result (sqlite3_context * context , NETWORK_RESULT * result ) {
297435 int rc = 0 ;
@@ -360,6 +498,9 @@ int network_extract_query_param(const char *query, const char *key, char *output
360498
361499 size_t key_len = strlen (key );
362500 const char * p = query ;
501+ #ifdef SQLITE_WASM_EXTRA_INIT
502+ if (* p == '?' ) p ++ ;
503+ #endif
363504
364505 while (p && * p ) {
365506 // Find the start of a key=value pair
@@ -394,6 +535,19 @@ int network_extract_query_param(const char *query, const char *key, char *output
394535}
395536
396537#ifndef CLOUDSYNC_OMIT_CURL
538+
539+ #ifdef SQLITE_WASM_EXTRA_INIT
540+ static char * substr (const char * start , const char * end ) {
541+ size_t len = end - start ;
542+ char * out = (char * )malloc (len + 1 );
543+ if (out ) {
544+ memcpy (out , start , len );
545+ out [len ] = 0 ;
546+ }
547+ return out ;
548+ }
549+ #endif
550+
397551bool network_compute_endpoints (sqlite3_context * context , network_data * data , const char * conn_string ) {
398552 // compute endpoints
399553 bool result = false;
@@ -410,12 +564,15 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
410564
411565 char * conn_string_https = NULL ;
412566
567+ #ifndef SQLITE_WASM_EXTRA_INIT
413568 CURLUcode rc = CURLUE_OUT_OF_MEMORY ;
414569 CURLU * url = curl_url ();
415570 if (!url ) goto finalize ;
571+ #endif
416572
417573 conn_string_https = cloudsync_string_replace_prefix (conn_string , "sqlitecloud://" , "https://" );
418574
575+ #ifndef SQLITE_WASM_EXTRA_INIT
419576 // set URL: https://UUID.g5.sqlite.cloud:443/chinook.sqlite?apikey=hWDanFolRT9WDK0p54lufNrIyfgLZgtMw6tb6fbPmpo
420577 rc = curl_url_set (url , CURLUPART_URL , conn_string_https , 0 );
421578 if (rc != CURLE_OK ) goto finalize ;
@@ -440,6 +597,38 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
440597 // apikey=hWDanFolRT9WDK0p54lufNrIyfgLZgtMw6tb6fbPmpo (OPTIONAL)
441598 rc = curl_url_get (url , CURLUPART_QUERY , & query , 0 );
442599 if (rc != CURLE_OK && rc != CURLUE_NO_QUERY ) goto finalize ;
600+ #else
601+ // Parse: scheme://host[:port]/path?query
602+ const char * p = strstr (conn_string_https , "://" );
603+ if (!p ) goto finalize ;
604+ scheme = substr (conn_string_https , p );
605+ p += 3 ;
606+ const char * host_start = p ;
607+ const char * host_end = strpbrk (host_start , ":/?" );
608+ if (!host_end ) goto finalize ;
609+ host = substr (host_start , host_end );
610+ p = host_end ;
611+ if (* p == ':' ) {
612+ ++ p ;
613+ const char * port_end = strpbrk (p , "/?" );
614+ if (!port_end ) goto finalize ;
615+ port = substr (p , port_end );
616+ p = port_end ;
617+ }
618+ if (* p == '/' ) {
619+ const char * path_start = p ;
620+ const char * path_end = strchr (path_start , '?' );
621+ if (!path_end ) path_end = path_start + strlen (path_start );
622+ database = substr (path_start , path_end );
623+ p = path_end ;
624+ }
625+ if (* p == '?' ) {
626+ query = strdup (p );
627+ }
628+ if (!scheme || !host || !database ) goto finalize ;
629+ if (!port ) port = strdup (CLOUDSYNC_DEFAULT_ENDPOINT_PORT );
630+ #define port_or_default port
631+ #endif
443632 if (query != NULL ) {
444633 char value [MAX_QUERY_VALUE_LEN ];
445634 if (!authentication && network_extract_query_param (query , "apikey" , value , sizeof (value )) == 0 ) {
@@ -463,8 +652,13 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
463652finalize :
464653 if (result == false) {
465654 // store proper result code/message
655+ #ifndef SQLITE_WASM_EXTRA_INIT
466656 if (rc != CURLE_OK ) sqlite3_result_error (context , curl_url_strerror (rc ), -1 );
467657 sqlite3_result_error_code (context , (rc != CURLE_OK ) ? SQLITE_ERROR : SQLITE_NOMEM );
658+ #else
659+ sqlite3_result_error (context , "URL parse error" , -1 );
660+ sqlite3_result_error_code (context , SQLITE_ERROR );
661+ #endif
468662
469663 // cleanup memory managed by the extension
470664 if (authentication ) cloudsync_memory_free (authentication );
@@ -485,13 +679,17 @@ bool network_compute_endpoints (sqlite3_context *context, network_data *data, co
485679 data -> upload_endpoint = upload_endpoint ;
486680 }
487681
488- // cleanup memory managed by libcurl
682+ // cleanup memory
683+ #ifndef SQLITE_WASM_EXTRA_INIT
684+ if (url ) curl_url_cleanup (url );
685+ #else
686+ #define curl_free (x ) free(x)
687+ #endif
489688 if (scheme ) curl_free (scheme );
490689 if (host ) curl_free (host );
491690 if (port ) curl_free (port );
492691 if (database ) curl_free (database );
493692 if (query ) curl_free (query );
494- if (url ) curl_url_cleanup (url );
495693 if (conn_string_https && conn_string_https != conn_string ) cloudsync_memory_free (conn_string_https );
496694
497695 return result ;
@@ -518,7 +716,7 @@ network_data *cloudsync_network_data(sqlite3_context *context) {
518716void cloudsync_network_init (sqlite3_context * context , int argc , sqlite3_value * * argv ) {
519717 DEBUG_FUNCTION ("cloudsync_network_init" );
520718
521- #ifndef CLOUDSYNC_OMIT_CURL
719+ #if !defined( CLOUDSYNC_OMIT_CURL ) && !defined( SQLITE_WASM_EXTRA_INIT )
522720 curl_global_init (CURL_GLOBAL_ALL );
523721 #endif
524722
@@ -583,7 +781,7 @@ void cloudsync_network_cleanup (sqlite3_context *context, int argc, sqlite3_valu
583781
584782 sqlite3_result_int (context , SQLITE_OK );
585783
586- #ifndef CLOUDSYNC_OMIT_CURL
784+ #if !defined( CLOUDSYNC_OMIT_CURL ) && !defined( SQLITE_WASM_EXTRA_INIT )
587785 curl_global_cleanup ();
588786 #endif
589787}
0 commit comments