PEBL 2.2
Psychology Experiment Building Language - Cross-platform psychological experiment development system
PEBLHTTP.cpp
Go to the documentation of this file.
1#ifdef PEBL_WIN32
2#include <winsock2.h> // Must be first to avoid conflicts
3#endif
4
5#include "PEBLHTTP.h"
6
7
8#ifdef PEBL_WIN32
9#include <windows.h>
10#endif
11
12
13//Set this to PEBL_HAPPY or PEBL_CURL (or PEBL_FETCH on emscripten) to use
14// alternate http libraries for get/post, etc.
15
16//set the HTTPLIB library we want to use for get/post
17//in the Makefile
18
19//Originally, we used happyhttp, but this seems to have some problems with
20//post commands when using simple file upload servers. The curl requires an
21//external dependency, but seems more robust. The happy branch is retained here
22// but going forward the curl version will be supported.
23
24#ifdef PEBL_HTTP
25
26#include "PError.h"
27#include <stdio.h>
28#include "../base/PList.h"
29#include "../base/PComplexData.h"
30#include "../utility/rc_ptrs.h"
31
32// HTTP_LIB is defined in the Makefile via -DHTTP_LIB=2 (or 3 for Emscripten)
33// Do not redefine it here
34
35#if HTTP_LIB == PEBL_HAPPY
36
37#include "happyhttp.h"
38
39int count=0;
40// This prints out to stdout; useful for POST where we don't care about the returned page.
41//
42void OnBegin( const happyhttp::Response* r, void* userdata )
43{
44 printf( "BEGIN (%d %s)\n", r->getstatus(), r->getreason() );
45 count = 0;
46}
47
48void OnData( const happyhttp::Response* r, void* userdata, const unsigned char* data, int n )
49{
50 fwrite( data,1,n, stdout );
51 count += n;
52}
53
54void OnComplete( const happyhttp::Response* r, void* userdata )
55{
56 printf( "COMPLETE (%d bytes)\n", count );
57}
58
59
60
61
62//These are used for the commands where we save to a file. userdata should contain
63//an PEBLHTTP object.get a file:
64void http_begin_cb( const happyhttp::Response* r,
65 void* userdata )
66{
67 //I don't think any of this is needed:
68 PEBLHTTP* http = (PEBLHTTP*)(userdata);
69 // FILE * filestream = http->GetFileObject();
70 printf( "BEGIN (%d %s)\n", r->getstatus(), r->getreason() );
71 http->SetByteCount(0);
72 http->SetStatus(r->getstatus());
73 http->SetReason(std::string(r->getreason()));
74
75}
76
77void http_getdata_cb( const happyhttp::Response* r, void* userdata,
78 const unsigned char* data, int n )
79{
80 std::cout << "http_getdata_cb 1\n";
81
82 PEBLHTTP* http = (PEBLHTTP*) (userdata);
83 FILE * filestream = http->GetFileObject();
84
85
86 fwrite( data,1,n, filestream);
87
88 http->SetByteCount(http->GetByteCount()+n);
89}
90
91void http_complete_cb( const happyhttp::Response* r, void* userdata )
92{
93 //. PEBLHTTP* http = (PEBLHTTP*) userdata;
94 // FILE * filestream = http->GetFileObject();
95 // fclose(filestream);
96
97 //printf( "COMPLETE (%d bytes)\n", http->GetByteCount() );
98 //printf( "COMPLETE (%d bytes)\n", 33);
99
100}
101
102//
103//These save the http retrieved to a s strstream for later use,
104// rather than a file.
105//
106
107
108//These are used for Get/ POST and PUT where we are returning text, not a file.
109//they should slurp the text into a text object that gets set to http->mText
110void http_begin_cb2( const happyhttp::Response* r,
111 void* userdata )
112{
113 //I don't think any of this is needed:
114 PEBLHTTP* http = (PEBLHTTP*)(userdata);
115 // FILE * filestream = http->GetFileObject();
116
117 //printf( "BEGIN (%d %s)\n", r->getstatus(), r->getreason() );
118 http->SetStatus(r->getstatus());
119 http->SetReason(std::string(r->getreason()));
120 http->SetByteCount(0);
121
122}
123
124void http_getdata_cb2( const happyhttp::Response* r, void* userdata,
125 const unsigned char* data, int n )
126{
127
128
129 //FILE * filestream = http->GetFileObject();
130 //FILE * filestream = (FILE*)userdata;
131
132 PEBLHTTP* http = (PEBLHTTP*) (userdata);
133 FILE * filestream = http->GetFileObject();
134
135 //This appends text
136 std::string * text = (http->GetTextObject());
137 text->append((const char*)data, n);
138
139 std::cout << "tmp text:" << *text << std::endl;
140
141 http->SetByteCount(http->GetByteCount()+n);
142}
143
144void http_complete_cb2( const happyhttp::Response* r, void* userdata )
145{
146 PEBLHTTP* http = (PEBLHTTP*) userdata;
147 std::string * text = http->GetTextObject();
148 FILE * filestream = http->GetFileObject();
149
150
151 printf( "COMPLETE (%d bytes)\n", http->GetByteCount() );
152 //printf( "COMPLETE (%d bytes)\n", 33);
153
154}
155
156
157
158PEBLHTTP::PEBLHTTP(Variant host, int port)
159{
160 mHost = host;
161 mPort = port;
162#ifdef WIN32
163 WSAData wsaData;
164 int code = WSAStartup(MAKEWORD(1, 1), &wsaData);
165 if( code != 0 )
166 {
167 PError::SignalFatalError(Variant("Unable to start http library.") + Variant(code));
168 }
169#endif //WIN32
170
171
172
173}
174
175
176PEBLHTTP::~PEBLHTTP()
177{
178}
179
180
181//This gets an http url and saves it to the specified file.
182//this assumes you are getting a binary-formatted file.
183int PEBLHTTP::GetHTTPFile(Variant filename,
184 Variant savename)
185{
186
187 // simple simple GET
188 happyhttp::Connection conn(mHost.GetString().c_str(), mPort );
189 //we should set binary/text here too?
190 //such as png, gif, jpg, .wav, .ogg, .mp3, etc?
191
192
193 //open file for binary/write access:
194 mFile = fopen(savename.GetString().c_str(),"wb");
195 if(mFile == NULL)
196 {
197 std::cerr << "Error opening file \n";
198 }
199 //Somehow, we need to 'prime' the file, or else it comes out
200 //of the void* cast as NULL.
201 fwrite( "",1,0,mFile);
202
203 conn.setcallbacks( http_begin_cb,
204 http_getdata_cb,
205 http_complete_cb, this);
206
207
208 try
209 {
210
211 std::cout << "trying to get file: " << filename.GetString() << std::endl;
212 conn.request( "GET", filename.GetString().c_str(), 0, 0,0 );
213 while( conn.outstanding() )
214 {
215 //std::cout << "+" << std::flush;
216 conn.pump();
217 }
218
219 fclose(mFile);
220 mFile = NULL;
221 return mStatus;
222
223 }
224 catch( happyhttp::Wobbly& e )
225 {
226 char s[300];
227 sprintf(s,"Exception:\n%s\n", e.what() );
229 fclose(mFile);
230 mFile = NULL;
231 return 0; //mstatus may not exist here
232 }
233
234}
235
236
237
238//This gets an http url and saves it to the specified file.
239//this assumes you are getting a binary-formatted file.
240std::string PEBLHTTP::GetHTTPText(Variant filename)
241{
242
243 // simple simple GET
244 happyhttp::Connection conn(mHost.GetString().c_str(), mPort );
245 //we should set binary/text here too?
246 //such as png, gif, jpg, .wav, .ogg, .mp3, etc?
247
248
249 conn.setcallbacks( http_begin_cb2,
250 http_getdata_cb2,
251 http_complete_cb2, this);
252
253 try
254 {
255
256 conn.request( "GET", filename.GetString().c_str(), 0, 0,0 );
257
258 while( conn.outstanding() )
259 {
260 //std::cout << "+" << std::flush;
261 conn.pump();
262 }
263
264 return mText;
265
266 }
267 catch( happyhttp::Wobbly& e )
268 {
269 std::cerr << "Wobbly caught\n";
270 char s[300];
271 sprintf(s,"Exception:\n%s\n", e.what() );
273 return mText; //mstatus may not exist here
274 }
275}
276
277
278
279Variant PEBLHTTP::PostHTTP(Variant name,
280 Variant args,
281 Variant bodyx)
282{
283
284
285 //mFile = fopen("tmp.html","wb");
286 // fwrite( "",1,0,mFile);
287
288
289
290
291
292
293 // simple POST command
294 happyhttp::Connection conn(mHost.GetString().c_str(), mPort );
295
296
297
298 //use the callback that puts the retrieved data in mText:
299 //conn.setcallbacks( http_begin_cb2,
300 //http_getdata_cb2,
301 //http_complete_cb2, this);
302
303 conn.setcallbacks( OnBegin, OnData, OnComplete, 0 );
304
305 //headersx is a variant-list, we need to create a const char* list
306 //to contain it (with a 0 at the end).
307 PError::AssertType(args, PEAT_LIST, "PostHTTP headers must be a list");
308
309 try
310 {
311
312 conn.putrequest("POST", name.GetString().c_str()); //name of page
313
314
315 // std::cout << headersx.GetComplexData() << std::endl;
316 // std::cout << (headersx.GetComplexData())->GetObject().get() << std::endl;
317 PList * dataList = (PList*)(args.GetComplexData()->GetPEBLObject().get());
318 //PComplexData * pcd = (headersx.GetComplexData());
319 //counted_ptr<PEBLObjectBase> pl = pcd->GetObject();
320 //PList * dataList = pl.get();
321 //(PList*)(pcd->GetObject().get());
322
323
324 std::vector<Variant>::iterator p1 = dataList->Begin();
325 std::vector<Variant>::iterator p1end = dataList->End();
326
327
328 while(p1 != p1end)
329 {
330 std::string head = *p1;
331 p1++;
332 std::string value = *p1;
333
334 p1++;
335 conn.putheader(head.c_str(),value.c_str()); //the content-length might need to be a number
336
337 }
338
339 conn.endheaders();
340
341
342
343 std::string body1 = bodyx.GetString();
344 const unsigned char* body = (unsigned char*)(body1.c_str());
345
346
347 unsigned int l = strlen(bodyx.GetString().c_str());
348
349 conn.send(body,l);
350
351
352 //The request is sent; now retrieve the response text:
353 while( conn.outstanding() )
354 {
355
356 std::cout << "outer-outstanding"<<conn.outstanding() << "+" <<std::endl;
357 conn.pump();
358
359 }
360
361 std::cout << "No more ouststanding data to process\n";
362
363 return mStatus;
364 }
365
366 catch( happyhttp::Wobbly& e )
367 {
368 char s[300];
369 sprintf(s,"Exception:\n%s\n", e.what() );
371
372
373 // fclose(mFile);
374 // mFile = NULL;
375 return mStatus;
376 }
377}
378
379
380
381Variant PEBLHTTP::PostMulti(Variant pagename,
382 Variant args,
383 Variant uploadname,
384 Variant form)
385{
386
387
388 //mFile = fopen("tmp.html","wb");
389 // fwrite( "",1,0,mFile);
390
391 return Variant("");
392
393}
394
395
396
397
398#elif HTTP_LIB == PEBL_CURL
399
403// CURL library for http support. This is an alternate that uses an external
404// library, rather than the lightweight happyhttp stuff which can be compiled
405// in. But it might be more robust.
410
411#include <stdio.h>
412#include <curl/curl.h>
413
414
415
416size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
417
418struct MemoryStruct {
419 char *memory;
420 size_t size;
421};
422
423static size_t WriteMemoryCallback(void *contents, size_t size,
424 size_t nmemb, void *userp)
425{
426 size_t realsize = size * nmemb;
427 struct MemoryStruct *mem = (struct MemoryStruct *)userp;
428
429 mem->memory = (char*)realloc(mem->memory, mem->size + realsize + 1);
430 if(mem->memory == NULL) {
431 /* out of memory! */
432 printf("not enough memory (realloc returned NULL)\n");
433 return 0;
434 }
435
436 memcpy(&(mem->memory[mem->size]), contents, realsize);
437 mem->size += realsize;
438 mem->memory[mem->size] = 0;
439
440 return realsize;
441}
442
443
444
445
446PEBLHTTP::PEBLHTTP(Variant host, int port)
447{
448 mHost = host;
449 mPort = port;
450
451
452 curl_global_init(CURL_GLOBAL_ALL);
453 mCurl = NULL;
454
455}
456
457
458PEBLHTTP::~PEBLHTTP()
459{
460}
461
462//
463//This gets an http url and saves it to the specified file.
464//this assumes you are getting a binary-formatted file.
465
466
467int PEBLHTTP::GetHTTPFile(Variant filename,
468 Variant savename)
469{
470
471 //establish the curl library
472
473 mCurl = curl_easy_init();
474 CURLcode res;
475 if(mCurl)
476 {
477 //set the basename url:
478 Variant fname = mHost + filename;
479 std::cout << "--------------*************Getting filename:" << fname << std::endl;
480 curl_easy_setopt(mCurl, CURLOPT_URL,fname.GetString().c_str());
481
482 /* specify port*/
483 curl_easy_setopt(mCurl, CURLOPT_PORT,mPort);
484
485 mFile = fopen(savename.GetString().c_str(),"wb");
486
487 //Somehow, we need to 'prime' the file, or else it comes out
488 //of the void* cast as NULL.
489 fwrite( "",1,0,mFile);
490
491
492 CURLcode ret;
493 //this will set the write function to save to mFile.
494 curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, mFile);
495 //ret = curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, write_callback);
496
497
498 /* First set the URL that is about to receive our POST. This URL can
499 just as well be a https: URL if that is what should receive the
500 data. */
501
502
503 /* use a GET to fetch this */
504 curl_easy_setopt(mCurl, CURLOPT_HTTPGET, 1L);
505
506 /* Perform the request */
507 ret = curl_easy_perform(mCurl);
508
509 if( CURLE_OK == ret)
510 {
511 char *ct;
512 res = curl_easy_setopt(mCurl, CURLOPT_HTTPGET, 1L);
513
514 if((CURLE_OK == res) && ct)
515 printf("We received Content-Type: %s\n", ct);
516 }
517 long http_code = 0;
518 curl_easy_getinfo(mCurl,CURLINFO_RESPONSE_CODE,&http_code);
519 mStatus = http_code;
520
521 /* always cleanup */
522 curl_easy_cleanup(mCurl);
523
524 fclose(mFile);
525 }
526 return mStatus;
527
528}
529
530
531
532 //This gets an http url and saves it to the specified file.
533 //this assumes you are getting a binary-formatted file.
534std::string PEBLHTTP::GetHTTPText(Variant filename)
535{
536
537
538 /* init the curl session */
539 curl_global_init(CURL_GLOBAL_ALL);
540 mCurl = curl_easy_init();
541 CURLcode res;
542
543 if(mCurl)
544 {
545 //set the basename url
546 Variant fname = mHost + filename;
547
548 struct MemoryStruct chunk;
549
550 chunk.memory = (char*)malloc(1); /* will be grown as needed by the realloc above */
551 chunk.size = 0; /* no data at this point */
552
553
554
555
556
557 /* specify URL to get */
558 curl_easy_setopt(mCurl, CURLOPT_URL, fname.GetString().c_str());
559
560 /* specify port*/
561 curl_easy_setopt(mCurl, CURLOPT_PORT,mPort);
562
563
564
565 /* send all data to this function */
566 curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
567
568 /* we pass our 'chunk' struct to the callback function */
569 curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, (void *)&chunk);
570
571 /* some servers don't like requests that are made without a user-agent
572 field, so we provide one */
573 curl_easy_setopt(mCurl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
574
575 /* get it! */
576 res = curl_easy_perform(mCurl);
577
578 /* check for errors */
579 if(res != CURLE_OK) {
580 fprintf(stderr, "curl_easy_perform() failed: %s\n",
581 curl_easy_strerror(res));
582 }
583 else {
584 /*
585 * Now, our chunk.memory points to a memory block that is chunk.size
586 * bytes big and contains the remote file.
587 *
588 * Do something nice with it!
589 */
590
591 printf("%lu bytes retrieved\n", (long)chunk.size);
592 mText = std::string((char*)(chunk.memory));
593 }
594
595 long http_code = 0;
596 curl_easy_getinfo(mCurl,CURLINFO_RESPONSE_CODE,&http_code);
597 mStatus = http_code;
598
599
600 /* cleanup curl stuff */
601 curl_easy_cleanup(mCurl);
602
603 free(chunk.memory);
604
605 /* we're done with libcurl, so clean it up */
606 curl_global_cleanup();
607
608 return mText;
609
610
611 }
612 return mText;
613}
614
615
616Variant PEBLHTTP::PostMulti(Variant pagename,
617 Variant args,
618 Variant uploadname,
619 Variant form)
620{
621
622
623 //mFile = fopen("tmp.html","wb");
624 // fwrite( "",1,0,mFile);
625
626
627
628
629 // Build full URL with protocol (http or https based on port)
630 std::string protocol = "http://";
631 if (mPort == 443) {
632 protocol = "https://";
633 }
634 Variant fname = Variant(protocol) + mHost + Variant(":") + Variant(mPort) + pagename;
635 const std::string form2 = form;
636 const std::string fname2 = fname;
637 const std::string upload2 = uploadname;
638
639
640 CURLcode res;
641
642 /* In windows, this will init the winsock stuff */
643 curl_global_init(CURL_GLOBAL_ALL);
644
645 /* get a curl handle */
646 if(!mCurl) mCurl = curl_easy_init();
647 if(mCurl)
648 {
649 /* First set the URL that is about to receive our POST. This URL can
650 just as well be a https:// URL if that is what should receive the
651 data. */
652 struct MemoryStruct chunk;
653 chunk.memory = (char*)malloc(1); /* will be grown as needed by the realloc above */
654 chunk.size = 0; /* no data at this point */
655
656
657
658 curl_mime *form_mime = NULL;
659 curl_mimepart *field = NULL;
660 struct curl_slist *headerlist=NULL;
661
662 /* Now specify the POST data */
663
664 //First, add the page arguments &a=b&c=d etc..
665 PError::AssertType(args, PEAT_LIST, "PostHTTP arguments must be a list");
666 PList * dataList = (PList*)(args.GetComplexData()->GetPEBLObject().get());
667
668 std::vector<Variant>::iterator p1 = dataList->Begin();
669 std::vector<Variant>::iterator p1end = dataList->End();
670
671 form_mime = curl_mime_init(mCurl);
672
673 while(p1 != p1end)
674 {
675 std::string head = *p1;
676 p1++;
677 std::string value = *p1;
678 p1++;
679 field = curl_mime_addpart(form_mime);
680 curl_mime_name(field, head.c_str());
681 curl_mime_data(field, value.c_str(), CURL_ZERO_TERMINATED);
682 }
683
684 /* Add the file field */
685 field = curl_mime_addpart(form_mime);
686 curl_mime_name(field, form2.c_str());
687 curl_mime_filedata(field, upload2.c_str());
688
689 /* Fill in the 'filename' field */
690 field = curl_mime_addpart(form_mime);
691 curl_mime_name(field, "filename");
692 curl_mime_data(field, upload2.c_str(), CURL_ZERO_TERMINATED);
693
694 /* Fill in the submit field */
695 field = curl_mime_addpart(form_mime);
696 curl_mime_name(field, "submit");
697 curl_mime_data(field, "send", CURL_ZERO_TERMINATED);
698
699
700 curl_easy_setopt(mCurl, CURLOPT_URL, fname2.c_str());
701
702 curl_easy_setopt(mCurl, CURLOPT_VERBOSE, 1L);
703
704 curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, headerlist);
705 curl_easy_setopt(mCurl, CURLOPT_MIMEPOST, form_mime);
706
707
708 /* send all data to this function */
709 curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
710
711 /* we pass our 'chunk' struct to the callback function */
712 curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, (void *)(&chunk));
713
714 /* some servers don't like requests that are made without a user-agent
715 field, so we provide one */
716 curl_easy_setopt(mCurl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
717
718
719 /* Perform the request, res will get the return code */
720 res = curl_easy_perform(mCurl);
721
722
723 /* Check for errors */
724 if(res != CURLE_OK)
725 {
726 fprintf(stderr, "PEBL HTTP Multi File POST failed: %s\n",
727 curl_easy_strerror(res));
728 }
729 else
730 {
731 /*
732 * Now, our chunk.memory points to a memory block that is chunk.size
733 * bytes big and contains the remote file.
734 *
735 * Do something nice with it!
736 */
737
738 printf("%lu bytes retrieved\n", (long)chunk.size);
739 mText = std::string((char*)(chunk.memory));
740 }
741
742
743
744
745 /* always cleanup */
746 curl_mime_free(form_mime);
747 curl_easy_cleanup(mCurl);
748 }
749 curl_global_cleanup();
750
751 return mText;
752
753}
754
755
756
757Variant PEBLHTTP::PostHTTP(Variant pagename,
758 Variant args,
759 Variant uploadname)
760
761{
762
763
764 //mFile = fopen("tmp.html","wb");
765 // fwrite( "",1,0,mFile);
766
767
768
769
770 // Build full URL with protocol (http or https based on port)
771 std::string protocol = "http://";
772 if (mPort == 443) {
773 protocol = "https://";
774 }
775 Variant fname = Variant(protocol) + mHost + Variant(":") + Variant(mPort) + pagename;
776
777
778 const std::string fname2 = fname;
779 const std::string upload2 = uploadname;
780
781
782 CURLcode res;
783
784 /* In windows, this will init the winsock stuff */
785 curl_global_init(CURL_GLOBAL_ALL);
786
787 /* get a curl handle */
788 if(!mCurl) mCurl = curl_easy_init();
789 if(mCurl)
790 {
791 /* First set the URL that is about to receive our POST. This URL can
792 just as well be a https:// URL if that is what should receive the
793 data. */
794 struct MemoryStruct chunk;
795 chunk.memory = (char*)malloc(1); /* will be grown as needed by the realloc above */
796 chunk.size = 0; /* no data at this point */
797
798
799
800 curl_mime *form_mime = NULL;
801 curl_mimepart *field = NULL;
802
803 /* Now specify the POST data */
804
805 //First, add the page arguments &a=b&c=d etc..
806 PError::AssertType(args, PEAT_LIST, "PostHTTP arguments must be a list");
807
808 PList * dataList = (PList*)(args.GetComplexData()->GetPEBLObject().get());
809
810 std::vector<Variant>::iterator p1 = dataList->Begin();
811 std::vector<Variant>::iterator p1end = dataList->End();
812
813 form_mime = curl_mime_init(mCurl);
814
815 while(p1 != p1end)
816 {
817 std::string head = *p1;
818 p1++;
819 std::string value = *p1;
820 p1++;
821 field = curl_mime_addpart(form_mime);
822 curl_mime_name(field, head.c_str());
823 curl_mime_data(field, value.c_str(), CURL_ZERO_TERMINATED);
824 }
825
826 /* Add the file field */
827 field = curl_mime_addpart(form_mime);
828 curl_mime_name(field, "sendfile");
829 curl_mime_filedata(field, upload2.c_str());
830
831 /* Fill in the filename field */
832 field = curl_mime_addpart(form_mime);
833 curl_mime_name(field, "filename");
834 curl_mime_data(field, upload2.c_str(), CURL_ZERO_TERMINATED);
835
836 /* Fill in the submit field */
837 field = curl_mime_addpart(form_mime);
838 curl_mime_name(field, "submit");
839 curl_mime_data(field, "send", CURL_ZERO_TERMINATED);
840
841
842 curl_easy_setopt(mCurl, CURLOPT_URL, fname2.c_str());
843
844 curl_easy_setopt(mCurl, CURLOPT_VERBOSE, 1L);
845
846 curl_easy_setopt(mCurl, CURLOPT_MIMEPOST, form_mime);
847
848
849 /* send all data to this function */
850 curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
851
852 /* we pass our 'chunk' struct to the callback function */
853 curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, (void *)(&chunk));
854
855 /* some servers don't like requests that are made without a user-agent
856 field, so we provide one */
857 curl_easy_setopt(mCurl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
858
859
860 /* Perform the request, res will get the return code */
861 res = curl_easy_perform(mCurl);
862
863
864 /* Check for errors */
865 if(res != CURLE_OK)
866 {
867 fprintf(stderr, "PEBL HTTP Multi File POST failed: %s\n",
868 curl_easy_strerror(res));
869 }
870 else
871 {
872 /*
873 * Now, our chunk.memory points to a memory block that is chunk.size
874 * bytes big and contains the remote file.
875 *
876 * Do something nice with it!
877 */
878
879 printf("%lu bytes retrieved\n", (long)chunk.size);
880 mText = std::string((char*)(chunk.memory));
881 }
882
883
884
885
886 /* always cleanup */
887 curl_mime_free(form_mime);
888 curl_easy_cleanup(mCurl);
889 }
890 curl_global_cleanup();
891
892 return mText;
893
894}
895#elif HTTP_LIB == PEBL_FETCH
896
897#include <emscripten.h>
898#include <emscripten/fetch.h>
899
902//
903// emscripten/fetch for http support.
904//
907
908#include <stdio.h>
909
910// Global completion flag for async uploads
911static volatile int upload_complete = 0;
912
913void uploadSucceeded(emscripten_fetch_t *fetch) {
914 printf("Finished uploading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
915 printf("Upload succeeded with status: %d\n", fetch->status);
916 upload_complete = 1; // Signal completion
917 // DON'T close here - let the main thread do it after extracting response
918}
919
920void uploadFailed(emscripten_fetch_t *fetch) {
921 printf("Uploading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
922 upload_complete = -1; // Signal failure
923 // DON'T close here - let the main thread do it
924}
925
926
927
928PEBLHTTP::PEBLHTTP(Variant host, int port)
929{
930 mHost = host;
931 mPort = port;
932}
933
934
935PEBLHTTP::~PEBLHTTP()
936{
937}
938
939//
940//This gets an http url and saves it to the specified file.
941//this assumes you are getting a binary-formatted file.
942
943
944int PEBLHTTP::GetHTTPFile(Variant filename,
945 Variant savename)
946{
948 // WARNING: This function still uses emscripten_fetch() which may not work
949 //
950 // This function has not been updated to use JavaScript fetch() and may
951 // fail with HTTP status 0 errors in recent Emscripten versions (see
952 // GitHub issue #25811, Nov 2025).
953 //
954 // If this function is needed, it should be rewritten following the same
955 // pattern as:
956 // - GetHTTPText() for text content with response.text()
957 // - PostMulti() for binary content with response.arrayBuffer()
958 //
959 // This function is currently not used by any battery tests. It exists for
960 // downloading binary stimulus files (images, sounds, videos) from third-party
961 // sources without PEBL hosting them. Only used in Transfer.pbl GetFiles().
963
964 // Set the basename url with protocol
965 std::string protocol = "http://";
966 if (mPort == 443) {
967 protocol = "https://";
968 }
969 Variant fname = Variant(protocol) + mHost + Variant(":") + Variant(mPort) + filename;
970
971 // Store URL as string to keep it alive during fetch
972 std::string url_str = fname.GetString();
973
974 emscripten_fetch_attr_t attr;
975 emscripten_fetch_attr_init(&attr);
976 strcpy(attr.requestMethod, "GET");
977
978 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;// | EMSCRIPTEN_FETCH_SYNCHRONOUS;
979 attr.onsuccess = uploadSucceeded;
980 attr.onerror = uploadFailed;
981
982 emscripten_fetch_t * fetch;
983
984
985 fetch = emscripten_fetch(&attr, url_str.c_str());
986 mStatus = fetch->status;
987
988 //The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
989
990 //This can probably be stored directly to the indexDB with the
991 // destinationpath property, instead of saving it to memory
992 // and then writing with fwrite.
993
994 fwrite(fetch->data,1,fetch->numBytes,mFile);
995 fclose(mFile);
996 emscripten_fetch_close(fetch);
997
998 return fetch->status;
999
1000}
1001
1002
1003
1004 //This gets an http url and returns it as text
1005std::string PEBLHTTP::GetHTTPText(Variant filename)
1006{
1008 // IMPORTANT: Why we use JavaScript Fetch API instead of emscripten_fetch
1009 //
1010 // This implementation uses browser's native Fetch API via EM_ASM instead
1011 // of Emscripten's emscripten_fetch API. This is necessary because:
1012 //
1013 // 1. Synchronous fetch broken: emscripten_fetch with EMSCRIPTEN_FETCH_SYNCHRONOUS
1014 // returns HTTP status 0 for same-origin requests (confirmed bug in
1015 // Emscripten 4.0.5-4.0.19, see GitHub issue #25811, Nov 2025).
1016 //
1017 // 2. Browser compatibility: Synchronous emscripten_fetch works in Chrome
1018 // (with warnings) but fails completely in Firefox and Safari.
1019 //
1020 // 3. Recommended pattern: Emscripten documentation recommends using JavaScript
1021 // fetch() with Asyncify for async operations in synchronous C++ code.
1022 //
1023 // 4. Asyncify: We use emscripten_sleep() in a busy-wait loop to handle the
1024 // async nature of fetch() while maintaining synchronous semantics expected
1025 // by PEBL code. This requires Asyncify to be enabled at compile time.
1026 //
1027 // See also: PostMulti() uses the same pattern for binary data handling.
1029
1030 // Set the basename url with protocol
1031 std::string protocol = "http://";
1032 if (mPort == 443) {
1033 protocol = "https://";
1034 }
1035 Variant fname = Variant(protocol) + mHost + Variant(":") + Variant(mPort) + Variant(filename);
1036 std::string url_str = fname.GetString();
1037
1038 printf("GetHTTPText: Fetching URL via JavaScript fetch(): [%s]\n", url_str.c_str());
1039
1040 // Initialize JavaScript completion flags
1041 EM_ASM({
1042 Module._js_get_complete = 0;
1043 Module._js_get_status = 0;
1044 Module._js_get_response = '';
1045 });
1046
1047 // Use JavaScript's Fetch API directly
1048 EM_ASM_INT({
1049 var url = UTF8ToString($0);
1050
1051 // Perform GET request with JavaScript fetch
1052 fetch(url, {
1053 method: 'GET'
1054 })
1055 .then(response => {
1056 Module._js_get_status = response.status;
1057 return response.text();
1058 })
1059 .then(text => {
1060 Module._js_get_response = text;
1061 Module._js_get_complete = 1;
1062 })
1063 .catch(error => {
1064 console.error('GET error:', error);
1065 Module._js_get_status = 0;
1066 Module._js_get_complete = -1;
1067 });
1068
1069 return 0;
1070 }, url_str.c_str());
1071
1072 // Busy-wait for JavaScript fetch to complete (requires Asyncify)
1073 int wait_count = 0;
1074 int js_complete = 0;
1075 while (js_complete == 0) {
1076 js_complete = EM_ASM_INT({
1077 return Module._js_get_complete || 0;
1078 });
1079
1080 if (js_complete == 0) {
1081 emscripten_sleep(50); // Sleep 50ms between checks
1082 wait_count++;
1083 if (wait_count > 600) { // 30 second timeout
1084 printf("GetHTTPText: Request timeout after 30 seconds\n");
1085 mStatus = 0;
1086 return "";
1087 }
1088 }
1089 }
1090
1091 // Get HTTP status from JavaScript
1092 mStatus = EM_ASM_INT({
1093 return Module._js_get_status || 0;
1094 });
1095
1096 if (js_complete < 0) {
1097 printf("GetHTTPText: Request failed\n");
1098 return "";
1099 }
1100
1101 // Get response text from JavaScript
1102 char* response_text = (char*)EM_ASM_INT({
1103 var text = Module._js_get_response || '';
1104 var len = lengthBytesUTF8(text) + 1;
1105 var buffer = _malloc(len);
1106 stringToUTF8(text, buffer, len);
1107 return buffer;
1108 });
1109
1110 if (response_text) {
1111 mText = std::string(response_text);
1112 printf("%lu bytes retrieved\n", (unsigned long)mText.length());
1113 free(response_text);
1114 } else {
1115 mText = "";
1116 }
1117
1118 return mText;
1119}
1120
1121
1122
1123Variant PEBLHTTP::PostHTTP(Variant pagename,
1124 Variant args,
1125 Variant uploadname)
1126
1127{
1129 // WARNING: This function still uses emscripten_fetch() which may not work
1130 //
1131 // This function has not been updated to use JavaScript fetch() and may
1132 // fail with HTTP status 0 errors in recent Emscripten versions (see
1133 // GitHub issue #25811, Nov 2025).
1134 //
1135 // If this function is needed, it should be rewritten following the same
1136 // pattern as GetHTTPText() and PostMulti() which use JavaScript fetch()
1137 // with Asyncify.
1138 //
1139 // This function is currently not used anywhere in PEBL code. File uploads
1140 // use PostHTTPFile() which internally calls PostMulti() (already fixed).
1142
1143
1144 //set the basename url:
1145
1146 emscripten_fetch_attr_t attr;
1147 emscripten_fetch_attr_init(&attr);
1148 strcpy(attr.requestMethod, "POST");
1149
1150 attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY ;//| EMSCRIPTEN_FETCH_SYNCHRONOUS;
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161 //First, add the page arguments &a=b&c=d etc..
1162 //to contain it (with a 0 at the end).
1163 PError::AssertType(args, PEAT_LIST, "PostHTTP arguments must be a list");
1164
1165 PList * dataList = (PList*)(args.GetComplexData()->GetPEBLObject().get());
1166
1167 std::vector<Variant>::iterator p1 = dataList->Begin();
1168 std::vector<Variant>::iterator p1end = dataList->End();
1169
1170 Variant arguments;
1171 Variant sep = "?";
1172 while(p1 != p1end)
1173 {
1174 std::string head = *p1;
1175 p1++;
1176 std::string value = *p1;
1177 p1++;
1178 arguments = arguments + sep + Variant(head) + Variant("=") + Variant(value);
1179 sep = "&";
1180 }
1181
1182 // Set the basename url with protocol
1183 std::string protocol = "http://";
1184 if (mPort == 443) {
1185 protocol = "https://";
1186 }
1187 Variant fname = Variant(protocol) + mHost + Variant(":") + Variant(mPort) + pagename + arguments;
1188
1189 // Store URL as string to keep it alive during fetch
1190 std::string url_str = fname.GetString();
1191
1192 /* Now specify the POST data */
1193 const char * const * rH;
1194
1195 /*
1196//These are headers that probably need to be added using the requestheaders
1197//list.
1198 curl_formadd(&formpost,
1199 &lastptr,
1200 CURLFORM_COPYNAME, "sendfile",
1201 CURLFORM_FILE, ((std::string)uploadname).c_str(),
1202 CURLFORM_END);
1203
1204 // Fill in the filename field
1205 curl_formadd(&formpost,
1206 &lastptr,
1207 CURLFORM_COPYNAME, "filename",
1208 CURLFORM_COPYCONTENTS, ((std::string)uploadname).c_str(),
1209 CURLFORM_END);
1210
1211 // Fill in the submit field too, even if this is rarely needed
1212 curl_formadd(&formpost,
1213 &lastptr,
1214 CURLFORM_COPYNAME, "submit",
1215 CURLFORM_COPYCONTENTS, "send",
1216 CURLFORM_END);
1217
1218 //set name and port:
1219 curl_easy_setopt(mCurl, CURLOPT_URL, ((std::string)fname).c_str());
1220 curl_easy_setopt(mCurl, CURLOPT_PORT,mPort);
1221
1222 curl_easy_setopt(mCurl, CURLOPT_VERBOSE, 1L);
1223 curl_easy_setopt(mCurl, CURLOPT_HTTPPOST, formpost);
1224
1225****/
1226
1227
1228
1229 emscripten_fetch_t * fetch;
1230 fetch = emscripten_fetch(&attr, url_str.c_str());
1231 mStatus = fetch->status;
1232
1233 //The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
1234
1235 char *p = (char*)malloc((fetch->numBytes+ 1 ) );
1236
1237 for(int i = 0; i < fetch->numBytes; ++i)
1238 p[i] = fetch->data[i];
1239 p[fetch->numBytes+1] = '\0';
1240
1241
1242 mText = std::string(p);
1243
1244 free(p);
1245 p=NULL;
1246
1247 emscripten_fetch_close(fetch);
1248
1249
1250
1251 return mText;
1252
1253}
1254
1255
1256
1257Variant PEBLHTTP::PostMulti(Variant pagename,
1258 Variant args,
1259 Variant uploadname,
1260 Variant form)
1261{
1263 // IMPORTANT: Why we use JavaScript Fetch API instead of emscripten_fetch
1264 //
1265 // This implementation uses browser's native Fetch API via EM_ASM instead
1266 // of Emscripten's emscripten_fetch API. This is necessary because:
1267 //
1268 // 1. Binary Data Handling: emscripten_fetch treats the requestData buffer
1269 // as a C string (null-terminated), even when requestDataSize is explicitly
1270 // set. This causes truncation at the first null byte (0x00) in binary data.
1271 //
1272 // 2. Multipart POST Bodies: When uploading files via multipart/form-data,
1273 // the POST body contains file content that may include null bytes. CSV
1274 // files, for example, often contain values that map to 0x00 bytes.
1275 //
1276 // 3. Solution: By copying data byte-by-byte into JavaScript's Uint8Array
1277 // and using the browser's native fetch() function, we bypass all C string
1278 // handling and ensure binary data (including null bytes) is sent correctly.
1279 //
1280 // 4. Asyncify: We use emscripten_sleep() in a busy-wait loop to handle the
1281 // async nature of fetch() while maintaining synchronous semantics expected
1282 // by PEBL code. This requires Asyncify to be enabled at compile time.
1284
1285 // Parse args list - these become form fields in the multipart body
1286 PError::AssertType(args, PEAT_LIST, "PostMulti arguments must be a list");
1287 PList * dataList = (PList*)(args.GetComplexData()->GetPEBLObject().get());
1288
1289 std::vector<Variant>::iterator p1 = dataList->Begin();
1290 std::vector<Variant>::iterator p1end = dataList->End();
1291
1292 // Store args to add as form fields later
1293 std::vector<std::pair<std::string, std::string>> form_fields;
1294 while(p1 != p1end)
1295 {
1296 std::string head = *p1;
1297 p1++;
1298 std::string value = *p1;
1299 p1++;
1300 form_fields.push_back(std::make_pair(head, value));
1301 }
1302
1303 // Build full URL with protocol (without query string - args go in body)
1304 std::string protocol = "http://";
1305 if (mPort == 443) {
1306 protocol = "https://";
1307 }
1308 Variant fname = Variant(protocol) + mHost + Variant(":") + Variant(mPort) + pagename;
1309
1310 // Read file content from virtual filesystem
1311 std::string uploadname_str = uploadname.GetString();
1312
1313 // Check file exists via Emscripten FS API
1314 // This ensures PEBL's writes are visible before we try C++ fopen()
1315 int fs_size = EM_ASM_INT({
1316 try {
1317 var path = UTF8ToString($0);
1318 var stat = FS.stat(path);
1319 return stat.size;
1320 } catch(e) {
1321 console.error('FS.stat error:', e);
1322 return -1;
1323 }
1324 }, uploadname_str.c_str());
1325
1326 if (fs_size <= 0) {
1327 PError::SignalWarning("PostMulti: File not found or empty: " + uploadname_str);
1328 return Variant("");
1329 }
1330
1331 FILE* file = fopen(uploadname_str.c_str(), "rb");
1332 if (!file) {
1333 PError::SignalWarning("PostMulti: Could not open file " + uploadname_str);
1334 return Variant("");
1335 }
1336
1337 // Get file size
1338 fseek(file, 0, SEEK_END);
1339 long filesize = ftell(file);
1340 fseek(file, 0, SEEK_SET);
1341
1342 if (filesize == 0) {
1343 fclose(file);
1344 PError::SignalWarning("PostMulti: File is empty (0 bytes): " + uploadname_str);
1345 return Variant("");
1346 }
1347
1348 // Read file content
1349 char* filedata = (char*)malloc(filesize);
1350 if (!filedata) {
1351 fclose(file);
1352 PError::SignalWarning("PostMulti: Memory allocation failed");
1353 return Variant("");
1354 }
1355
1356 size_t bytes_read = fread(filedata, 1, filesize, file);
1357 fclose(file);
1358
1359 if (bytes_read != filesize) {
1360 free(filedata);
1361 PError::SignalWarning("PostMulti: File read error");
1362 return Variant("");
1363 }
1364
1365 // Build multipart/form-data body
1366 std::string boundary = "----PEBLFormBoundary7MA4YWxkTrZu0gW";
1367 std::string form_str = form.GetString();
1368
1369 // Extract just the filename from the path
1370 std::string filename = uploadname_str;
1371 size_t last_slash = filename.find_last_of("/\\");
1372 if (last_slash != std::string::npos) {
1373 filename = filename.substr(last_slash + 1);
1374 }
1375
1376 // Build multipart body
1377 std::string body;
1378
1379 // Add form field parameters (user_name, upload_password, taskname, subnum, etc.)
1380 for (size_t i = 0; i < form_fields.size(); i++) {
1381 body += "--" + boundary + "\r\n";
1382 body += "Content-Disposition: form-data; name=\"" + form_fields[i].first + "\"\r\n\r\n";
1383 body += form_fields[i].second + "\r\n";
1384 }
1385
1386 // Add file upload field
1387 body += "--" + boundary + "\r\n";
1388 body += "Content-Disposition: form-data; name=\"" + form_str + "\"; filename=\"" + filename + "\"\r\n";
1389 body += "Content-Type: application/octet-stream\r\n\r\n";
1390 body.append(filedata, filesize);
1391 body += "\r\n";
1392
1393 // Add filename field
1394 body += "--" + boundary + "\r\n";
1395 body += "Content-Disposition: form-data; name=\"filename\"\r\n\r\n";
1396 body += filename + "\r\n";
1397
1398 // Add submit field
1399 body += "--" + boundary + "\r\n";
1400 body += "Content-Disposition: form-data; name=\"submit\"\r\n\r\n";
1401 body += "send\r\n";
1402
1403 // Final boundary
1404 body += "--" + boundary + "--\r\n";
1405
1406 free(filedata);
1407
1408 // Store URL as string to keep it alive during fetch
1409 std::string url_str = fname.GetString();
1410
1411 // Allocate persistent storage for headers
1412 static std::string content_type_header;
1413 content_type_header = "multipart/form-data; boundary=" + boundary;
1414
1415 // Allocate persistent buffer for POST body
1416 size_t body_len = body.length();
1417 char* persistent_body = (char*)malloc(body_len);
1418 if (!persistent_body) {
1419 PError::SignalWarning("PostMulti: Failed to allocate body buffer");
1420 return Variant("");
1421 }
1422
1423 // Use body.data() (not c_str()) to handle binary data correctly
1424 memcpy(persistent_body, body.data(), body_len);
1425
1426 // Initialize JavaScript completion flags
1427 EM_ASM({
1428 Module._js_fetch_complete = 0;
1429 Module._js_fetch_status = 0;
1430 Module._js_fetch_response = '';
1431 });
1432
1433 // Use JavaScript's Fetch API directly with Uint8Array
1434 EM_ASM_INT({
1435 var url = UTF8ToString($0);
1436 var bodyPtr = $1;
1437 var bodyLen = $2;
1438 var contentType = UTF8ToString($3);
1439
1440 // Copy POST body from C++ memory to JavaScript Uint8Array (binary-safe)
1441 var bodyData = new Uint8Array(bodyLen);
1442 for (var i = 0; i < bodyLen; i++) {
1443 bodyData[i] = HEAPU8[bodyPtr + i];
1444 }
1445
1446 // Perform fetch with binary body
1447 fetch(url, {
1448 method: 'POST',
1449 headers: {
1450 'Content-Type': contentType
1451 },
1452 body: bodyData
1453 })
1454 .then(response => {
1455 Module._js_fetch_status = response.status;
1456 return response.text();
1457 })
1458 .then(text => {
1459 Module._js_fetch_response = text;
1460 Module._js_fetch_complete = 1;
1461 })
1462 .catch(error => {
1463 console.error('Upload error:', error);
1464 Module._js_fetch_status = 0;
1465 Module._js_fetch_complete = -1;
1466 });
1467
1468 return 0;
1469 }, url_str.c_str(), (int)persistent_body, (int)body_len, content_type_header.c_str());
1470
1471 // Busy-wait for JavaScript fetch to complete (requires Asyncify)
1472 int wait_count = 0;
1473 int js_complete = 0;
1474 while (js_complete == 0) {
1475 js_complete = EM_ASM_INT({
1476 return Module._js_fetch_complete || 0;
1477 });
1478
1479 if (js_complete == 0) {
1480 emscripten_sleep(50); // Sleep 50ms between checks
1481 wait_count++;
1482 if (wait_count > 600) { // 30 second timeout
1483 free(persistent_body);
1484 PError::SignalWarning("PostMulti: Upload timeout after 30 seconds");
1485 return Variant("");
1486 }
1487 }
1488 }
1489
1490 free(persistent_body);
1491
1492 // Get HTTP status from JavaScript
1493 mStatus = EM_ASM_INT({
1494 return Module._js_fetch_status || 0;
1495 });
1496
1497 if (js_complete < 0) {
1498 return Variant("Upload failed");
1499 }
1500
1501 // Get response text from JavaScript
1502 char* response_text = (char*)EM_ASM_INT({
1503 var text = Module._js_fetch_response || '';
1504 var len = lengthBytesUTF8(text) + 1;
1505 var buffer = _malloc(len);
1506 stringToUTF8(text, buffer, len);
1507 return buffer;
1508 });
1509
1510 if (response_text) {
1511 mText = std::string(response_text);
1512 free(response_text);
1513 } else {
1514 mText = "";
1515 }
1516
1517 return mText;
1518}
1519
1520
1521
1522#endif
1523#endif
#define NULL
Definition BinReloc.cpp:317
PNode * head
Definition PEBL.cpp:220
@ PEAT_LIST
Definition PError.h:61
counted_ptr< PEBLObjectBase > GetPEBLObject() const
Definition PList.h:45
std::vector< Variant >::const_iterator End() const
Definition PList.cpp:132
std::vector< Variant >::const_iterator Begin() const
Definition PList.cpp:127
std::string GetString() const
Definition Variant.cpp:1056
PComplexData * GetComplexData() const
Definition Variant.cpp:1299
X * get() const
Definition rc_ptrs.h:110
int getstatus() const
const char * getreason() const
const char * what() const
Definition happyhttp.h:129
void SignalWarning(const std::string &message)
Definition PError.cpp:119
void AssertType(Variant v, int type, const std::string &outsidemessage)
void SignalFatalError(const std::string &message)
void OnData(const happyhttp::Response *r, void *userdata, const unsigned char *data, int n)
Definition test.cpp:20
int count
Definition test.cpp:12
void OnBegin(const happyhttp::Response *r, void *userdata)
Definition test.cpp:14
void OnComplete(const happyhttp::Response *r, void *userdata)
Definition test.cpp:26