harden HTTP response handling
This commit is contained in:
@@ -29,7 +29,7 @@ Verified in this checkout on 2026-09-22:
|
|||||||
|
|
||||||
This run's narrow memory slice releases the previous deep value when a runtime variable or map entry is overwritten, and releases temporary split argument arrays and strings from `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM`. Focused coverage is `tools/validate_memory_cleanup.py`; it reports `PASS: expression token allocations are released` under an ASan/UBSan build with LeakSanitizer checking and exercises repeated four-argument calls plus string/list/map overwrites. Remaining memory-growth areas include final runtime teardown, loaded program storage, and other expression temporaries. The `RUN COMMAND` path remains trusted shell execution, not a sandbox.
|
This run's narrow memory slice releases the previous deep value when a runtime variable or map entry is overwritten, and releases temporary split argument arrays and strings from `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM`. Focused coverage is `tools/validate_memory_cleanup.py`; it reports `PASS: expression token allocations are released` under an ASan/UBSan build with LeakSanitizer checking and exercises repeated four-argument calls plus string/list/map overwrites. Remaining memory-growth areas include final runtime teardown, loaded program storage, and other expression temporaries. The `RUN COMMAND` path remains trusted shell execution, not a sandbox.
|
||||||
|
|
||||||
The `RUN COMMAND` path now decodes POSIX `pclose()` wait status before storing `LASTEXIT`, so a child that exits with code 3 exposes `3` rather than the encoded status `768`. Focused coverage is `tests/42_last_exit_code.claro`. This slice does not yet fix HTTP buffering/status handling or the remaining memory-growth boundaries. Claro remains a trusted-script interpreter, not a sandbox.
|
The `RUN COMMAND` path now decodes POSIX `pclose()` wait status before storing `LASTEXIT`, so a child that exits with code 3 exposes `3` rather than the encoded status `768`. Focused coverage is `tests/42_last_exit_code.claro`. HTTP responses now have a 1,048,576-byte cap and marker-like response bodies are preserved while extracting the final HTTP status marker. Focused coverage is `tools/validate_http_hardening.py`. Remaining memory-growth areas include final runtime teardown, loaded program storage, and other expression temporaries. Claro remains a trusted-script interpreter, not a sandbox.
|
||||||
|
|
||||||
## Feature matrix
|
## Feature matrix
|
||||||
|
|
||||||
|
|||||||
@@ -30,3 +30,15 @@ Runtime variables and map entries own deep copies of their values. Replacing an
|
|||||||
Command argument lists created by `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM` are temporary parser storage. They now share one cleanup helper, so repeated calls do not retain the duplicated argument strings or pointer array. The helper does not change argument evaluation or syntax compatibility.
|
Command argument lists created by `DO`, `CALL`, `TEXT ... CONTAINS`, and `RANDOM` are temporary parser storage. They now share one cleanup helper, so repeated calls do not retain the duplicated argument strings or pointer array. The helper does not change argument evaluation or syntax compatibility.
|
||||||
|
|
||||||
Focused verification builds with AddressSanitizer/UndefinedBehaviorSanitizer, repeatedly exercises a four-argument `DO`, and checks the cleanup helper before confirming the existing string, list, and map overwrite behavior.
|
Focused verification builds with AddressSanitizer/UndefinedBehaviorSanitizer, repeatedly exercises a four-argument `DO`, and checks the cleanup helper before confirming the existing string, list, and map overwrite behavior.
|
||||||
|
|
||||||
|
## HTTP response handling
|
||||||
|
|
||||||
|
HTTP responses are capped at 1,048,576 bytes. Exceeding the cap produces a beginner-facing runtime error instead of retaining an unbounded response. The curl status suffix is taken from the final status marker, so a response body containing marker-like text is preserved. Existing `HTTP CHECK` URL safety rules remain unchanged.
|
||||||
|
|
||||||
|
Focused verification:
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 tools/validate_http_hardening.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This validator uses a local HTTP server to check marker-like response text, status `200`, and the oversized-response diagnostic.
|
||||||
|
|||||||
+23
-12
@@ -198,20 +198,29 @@ static char *text_lower_copy(const char *s){ char *o=xstrdup(s?s:""); int i; for
|
|||||||
static char *text_trim_copy(const char *s){ char *o=xstrdup(s?s:""); char *t=trim_inplace(o); char *r=xstrdup(t); free(o); return r; }
|
static char *text_trim_copy(const char *s){ char *o=xstrdup(s?s:""); char *t=trim_inplace(o); char *r=xstrdup(t); free(o); return r; }
|
||||||
static int str_starts_with(const char *s,const char *prefix){ size_t n=strlen(prefix?prefix:""); return strncmp(s?s:"",prefix?prefix:"",n)==0; }
|
static int str_starts_with(const char *s,const char *prefix){ size_t n=strlen(prefix?prefix:""); return strncmp(s?s:"",prefix?prefix:"",n)==0; }
|
||||||
static int str_ends_with(const char *s,const char *suffix){ size_t a=strlen(s?s:""), b=strlen(suffix?suffix:""); return b<=a && strcmp((s?s:"")+a-b,suffix?suffix:"")==0; }
|
static int str_ends_with(const char *s,const char *suffix){ size_t a=strlen(s?s:""), b=strlen(suffix?suffix:""); return b<=a && strcmp((s?s:"")+a-b,suffix?suffix:"")==0; }
|
||||||
static char *capture_command_output(const char *cmd,int *exit_code){
|
static char *capture_command_output(const char *cmd,int *exit_code,size_t max_bytes){
|
||||||
Str out; char buf[512]; FILE *fp; str_init(&out);
|
Str out; char buf[512]; FILE *fp; int capped=0; str_init(&out);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
fp=_popen(cmd,"r");
|
fp=_popen(cmd,"r");
|
||||||
#else
|
#else
|
||||||
fp=popen(cmd,"r");
|
fp=popen(cmd,"r");
|
||||||
#endif
|
#endif
|
||||||
if(!fp){ if(exit_code) *exit_code=-1; return xstrdup(""); }
|
if(!fp){ if(exit_code) *exit_code=-1; return xstrdup(""); }
|
||||||
while(fgets(buf,sizeof(buf),fp)) str_add(&out,buf);
|
while(fgets(buf,sizeof(buf),fp)){
|
||||||
|
size_t n=strlen(buf);
|
||||||
|
if(max_bytes && out.len+n>max_bytes){
|
||||||
|
if(out.len<max_bytes){ size_t keep=max_bytes-out.len; char *part=substr(buf,buf+keep); str_add(&out,part); free(part); }
|
||||||
|
capped=1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(!capped) str_add(&out,buf);
|
||||||
|
}
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
if(exit_code) *exit_code=_pclose(fp);
|
if(exit_code && !capped) *exit_code=_pclose(fp); else _pclose(fp);
|
||||||
#else
|
#else
|
||||||
if(exit_code){ int status=pclose(fp); if(WIFEXITED(status)) *exit_code=WEXITSTATUS(status); else if(WIFSIGNALED(status)) *exit_code=128+WTERMSIG(status); else *exit_code=-1; }
|
{ int status=pclose(fp); if(exit_code && !capped){ if(WIFEXITED(status)) *exit_code=WEXITSTATUS(status); else if(WIFSIGNALED(status)) *exit_code=128+WTERMSIG(status); else *exit_code=-1; } }
|
||||||
#endif
|
#endif
|
||||||
|
if(capped && exit_code) *exit_code=-2;
|
||||||
return str_take(&out);
|
return str_take(&out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,13 +242,15 @@ static char *claro_mock_http(const char *url,int *status){
|
|||||||
if(status) *status=404; return xstrdup("Claro mock URL was not found.");
|
if(status) *status=404; return xstrdup("Claro mock URL was not found.");
|
||||||
}
|
}
|
||||||
static char *claro_http_get_text(const char *url,int *http_status,int *exit_code){
|
static char *claro_http_get_text(const char *url,int *http_status,int *exit_code){
|
||||||
char cmd[2300]; char *raw,*mark,*body; const char *marker="\n__CLARO_HTTP_STATUS__";
|
char cmd[2300]; char *raw,*mark,*body; const char *marker="\n__CLARO_HTTP_STATUS__"; size_t max_bytes=1024*1024;
|
||||||
if(http_status) *http_status=0;
|
if(http_status) *http_status=0;
|
||||||
if(exit_code) *exit_code=0;
|
if(exit_code) *exit_code=0;
|
||||||
if(starts_ci(url,"claro://")) return claro_mock_http(url,http_status);
|
if(starts_ci(url,"claro://")) return claro_mock_http(url,http_status);
|
||||||
snprintf(cmd,sizeof(cmd),"curl -L -s -m 15 -w \"\\n__CLARO_HTTP_STATUS__%%{http_code}\" \"%s\"",url);
|
snprintf(cmd,sizeof(cmd),"curl -L -s -m 15 -w \"\\n__CLARO_HTTP_STATUS__%%{http_code}\" \"%s\"",url);
|
||||||
raw=capture_command_output(cmd,exit_code);
|
raw=capture_command_output(cmd,exit_code,max_bytes);
|
||||||
mark=strstr(raw,marker);
|
if(exit_code&&*exit_code==-2){ free(raw); return xstrdup(""); }
|
||||||
|
mark=NULL;
|
||||||
|
{ char *scan=raw; while((scan=strstr(scan,marker))!=NULL){ mark=scan; scan++; } }
|
||||||
if(mark){ *mark=0; if(http_status) *http_status=atoi(mark+strlen(marker)); body=xstrdup(raw); free(raw); return body; }
|
if(mark){ *mark=0; if(http_status) *http_status=atoi(mark+strlen(marker)); body=xstrdup(raw); free(raw); return body; }
|
||||||
if(http_status) *http_status=0;
|
if(http_status) *http_status=0;
|
||||||
return raw;
|
return raw;
|
||||||
@@ -390,17 +401,17 @@ static void exec_line(Runtime *rt,Program *p,int *pcp,const char *raw){ char buf
|
|||||||
if(as&&starts_ci(t+4," GET")){
|
if(as&&starts_ci(t+4," GET")){
|
||||||
char *ue=substr(t+8,as); char *after=xstrdup(trim_inplace((char*)as+2)); const char *status_word=find_word_ci(after,"STATUS"); char *var=status_word?substr(after,status_word):xstrdup(after); char *status_var=status_word?xstrdup(trim_inplace((char*)status_word+6)):NULL; Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); int code=0,http=0; char *out;
|
char *ue=substr(t+8,as); char *after=xstrdup(trim_inplace((char*)as+2)); const char *status_word=find_word_ci(after,"STATUS"); char *var=status_word?substr(after,status_word):xstrdup(after); char *status_var=status_word?xstrdup(trim_inplace((char*)status_word+6)):NULL; Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); int code=0,http=0; char *out;
|
||||||
if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP GET needs a safe http://, https://, or claro:// URL."); free(ue); free(after); free(var); free(status_var); free(url); return; }
|
if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP GET needs a safe http://, https://, or claro:// URL."); free(ue); free(after); free(var); free(status_var); free(url); return; }
|
||||||
out=claro_http_get_text(url,&http,&code); rt_set(rt,trim_inplace(var),v_str(out)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); if(status_var&&*status_var) rt_set(rt,status_var,v_num(http)); free(out); free(url); free(ue); free(after); free(var); free(status_var); return;
|
out=claro_http_get_text(url,&http,&code); if(code==-2){ rt_error(rt,p->path,pc+1,"HTTP response exceeds the safe size limit of 1048576 bytes."); free(out); free(url); free(ue); free(after); free(var); free(status_var); return; } rt_set(rt,trim_inplace(var),v_str(out)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); if(status_var&&*status_var) rt_set(rt,status_var,v_num(http)); free(out); free(url); free(ue); free(after); free(var); free(status_var); return;
|
||||||
}
|
}
|
||||||
if(as&&starts_ci(t+4," STATUS")){
|
if(as&&starts_ci(t+4," STATUS")){
|
||||||
char *ue=substr(t+11,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); int code=0,http=0; char *out;
|
char *ue=substr(t+11,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); int code=0,http=0; char *out;
|
||||||
if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP STATUS needs a safe http://, https://, or claro:// URL."); free(ue); free(var); free(url); return; }
|
if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP STATUS needs a safe http://, https://, or claro:// URL."); free(ue); free(var); free(url); return; }
|
||||||
out=claro_http_get_text(url,&http,&code); rt_set(rt,trim_inplace(var),v_num(http)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); free(out); free(url); free(ue); free(var); return;
|
out=claro_http_get_text(url,&http,&code); if(code==-2){ rt_error(rt,p->path,pc+1,"HTTP response exceeds the safe size limit of 1048576 bytes."); free(out); free(url); free(ue); free(var); return; } rt_set(rt,trim_inplace(var),v_num(http)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); free(out); free(url); free(ue); free(var); return;
|
||||||
}
|
}
|
||||||
if(starts_ci(t+4," SAVE")){
|
if(starts_ci(t+4," SAVE")){
|
||||||
const char *to=find_word_ci(t,"TO"); const char *as2=find_word_ci(t,"AS"); if(to){ char *ue=substr(t+9,to); char *pe=as2?substr(to+2,as2):xstrdup(to+2); char *status_var=as2?xstrdup(trim_inplace((char*)as2+2)):NULL; Value uv=eval_expr(rt,ue), pv=eval_expr(rt,pe); char *url=v_to_string(uv), *path=v_to_string(pv); int code=0,http=0; char *out;
|
const char *to=find_word_ci(t,"TO"); const char *as2=find_word_ci(t,"AS"); if(to){ char *ue=substr(t+9,to); char *pe=as2?substr(to+2,as2):xstrdup(to+2); char *status_var=as2?xstrdup(trim_inplace((char*)as2+2)):NULL; Value uv=eval_expr(rt,ue), pv=eval_expr(rt,pe); char *url=v_to_string(uv), *path=v_to_string(pv); int code=0,http=0; char *out;
|
||||||
if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP SAVE needs a safe http://, https://, or claro:// URL."); free(ue); free(pe); free(status_var); free(url); free(path); return; }
|
if(!claro_url_is_safe(url)){ rt_error(rt,p->path,pc+1,"HTTP SAVE needs a safe http://, https://, or claro:// URL."); free(ue); free(pe); free(status_var); free(url); free(path); return; }
|
||||||
out=claro_http_get_text(url,&http,&code); if(!write_text_bytes(path,out)) rt_error(rt,p->path,pc+1,"Could not save HTTP response to file."); if(status_var&&*status_var) rt_set(rt,status_var,v_num(http)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); free(out); free(url); free(path); free(ue); free(pe); free(status_var); return; }
|
out=claro_http_get_text(url,&http,&code); if(code==-2){ rt_error(rt,p->path,pc+1,"HTTP response exceeds the safe size limit of 1048576 bytes."); free(out); free(url); free(path); free(ue); free(pe); free(status_var); return; } if(!write_text_bytes(path,out)) rt_error(rt,p->path,pc+1,"Could not save HTTP response to file."); if(status_var&&*status_var) rt_set(rt,status_var,v_num(http)); rt_set(rt,"LASTEXIT",v_num(code)); rt_set(rt,"LASTHTTP",v_num(http)); free(out); free(url); free(path); free(ue); free(pe); free(status_var); return; }
|
||||||
}
|
}
|
||||||
if(as&&starts_ci(t+4," CHECK")){
|
if(as&&starts_ci(t+4," CHECK")){
|
||||||
char *ue=substr(t+10,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); rt_set(rt,var,v_bool(claro_url_is_safe(url))); free(url); free(ue); free(var); return;
|
char *ue=substr(t+10,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value uv=eval_expr(rt,ue); char *url=v_to_string(uv); rt_set(rt,var,v_bool(claro_url_is_safe(url))); free(url); free(ue); free(var); return;
|
||||||
@@ -441,7 +452,7 @@ static void exec_line(Runtime *rt,Program *p,int *pcp,const char *raw){ char buf
|
|||||||
else { char *ex=substr(pcur,as); Value v=eval_expr(rt,ex); char *txt=v_to_string(v); if(!strcmp(restup,"UPPER")){ char *r=text_upper_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"LOWER")){ char *r=text_lower_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"TRIM")){ char *r=text_trim_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"LENGTH")){ rt_set(rt,var,v_num((double)strlen(txt))); } else if(!strcmp(restup,"CONTAINS")){ char *left=NULL,*right=NULL; const char *needle=find_word_ci(pcur,"NEEDLE"); if(!needle) needle=find_word_ci(pcur,"TEXT"); if(needle){ left=substr(pcur,needle); right=substr(needle+6,as); } else { char **args=NULL; int n=split_args(pcur,&args); if(n>=2){ left=xstrdup(args[0]); right=substr(args[1],as); } split_args_free(args,n); } if(left&&right){ Value lv=eval_expr(rt,left), rv=eval_expr(rt,right); char *ls=v_to_string(lv), *rs=v_to_string(rv); rt_set(rt,var,v_bool(strstr(ls,rs)!=NULL)); free(ls); free(rs); } free(left); free(right); } free(txt); free(ex); }
|
else { char *ex=substr(pcur,as); Value v=eval_expr(rt,ex); char *txt=v_to_string(v); if(!strcmp(restup,"UPPER")){ char *r=text_upper_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"LOWER")){ char *r=text_lower_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"TRIM")){ char *r=text_trim_copy(txt); rt_set(rt,var,v_str(r)); free(r); } else if(!strcmp(restup,"LENGTH")){ rt_set(rt,var,v_num((double)strlen(txt))); } else if(!strcmp(restup,"CONTAINS")){ char *left=NULL,*right=NULL; const char *needle=find_word_ci(pcur,"NEEDLE"); if(!needle) needle=find_word_ci(pcur,"TEXT"); if(needle){ left=substr(pcur,needle); right=substr(needle+6,as); } else { char **args=NULL; int n=split_args(pcur,&args); if(n>=2){ left=xstrdup(args[0]); right=substr(args[1],as); } split_args_free(args,n); } if(left&&right){ Value lv=eval_expr(rt,left), rv=eval_expr(rt,right); char *ls=v_to_string(lv), *rs=v_to_string(rv); rt_set(rt,var,v_bool(strstr(ls,rs)!=NULL)); free(ls); free(rs); } free(left); free(right); } free(txt); free(ex); }
|
||||||
free(var); } return; }
|
free(var); } return; }
|
||||||
if(strcmp(up,"RANDOM")==0){ const char *as=find_word_ci(t,"AS"); if(as&&starts_ci(t+6," NUMBER")){ char *range=substr(t+13,as); char *var=xstrdup(trim_inplace((char*)as+2)); char **args=NULL; int n=split_args(range,&args); double a=1,b=100; if(n>=2){ a=v_number(eval_expr(rt,args[0])); b=v_number(eval_expr(rt,args[1])); } else { char *tmp=range; char *one=unquote_token((const char**)&tmp); char *two=unquote_token((const char**)&tmp); if(*one) a=v_number(eval_expr(rt,one)); if(*two) b=v_number(eval_expr(rt,two)); free(one); free(two); } split_args_free(args,n); if(b<a){ double c=a; a=b; b=c; } rt_set(rt,var,v_num((int)a + (rand()%((int)(b-a+1))))); free(range); free(var); } return; }
|
if(strcmp(up,"RANDOM")==0){ const char *as=find_word_ci(t,"AS"); if(as&&starts_ci(t+6," NUMBER")){ char *range=substr(t+13,as); char *var=xstrdup(trim_inplace((char*)as+2)); char **args=NULL; int n=split_args(range,&args); double a=1,b=100; if(n>=2){ a=v_number(eval_expr(rt,args[0])); b=v_number(eval_expr(rt,args[1])); } else { char *tmp=range; char *one=unquote_token((const char**)&tmp); char *two=unquote_token((const char**)&tmp); if(*one) a=v_number(eval_expr(rt,one)); if(*two) b=v_number(eval_expr(rt,two)); free(one); free(two); } split_args_free(args,n); if(b<a){ double c=a; a=b; b=c; } rt_set(rt,var,v_num((int)a + (rand()%((int)(b-a+1))))); free(range); free(var); } return; }
|
||||||
if(strcmp(up,"RUN")==0){ const char *as=find_word_ci(t,"AS"); if(as&&starts_ci(t+3," COMMAND")){ char *ex=substr(t+11,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value cv=eval_expr(rt,ex); char *cmd=v_to_string(cv); int code=0; char *out=capture_command_output(cmd,&code); rt_set(rt,var,v_str(out)); rt_set(rt,"LASTEXIT",v_num(code)); free(out); free(cmd); free(ex); free(var); } return; }
|
if(strcmp(up,"RUN")==0){ const char *as=find_word_ci(t,"AS"); if(as&&starts_ci(t+3," COMMAND")){ char *ex=substr(t+11,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value cv=eval_expr(rt,ex); char *cmd=v_to_string(cv); int code=0; char *out=capture_command_output(cmd,&code,0); rt_set(rt,var,v_str(out)); rt_set(rt,"LASTEXIT",v_num(code)); free(out); free(cmd); free(ex); free(var); } return; }
|
||||||
if(strcmp(up,"SORT")==0){ char *name=xstrdup(trim_inplace(t+4)); Value v=rt_get(rt,name); value_list_sort(v); rt_set(rt,name,v); free(name); return; }
|
if(strcmp(up,"SORT")==0){ char *name=xstrdup(trim_inplace(t+4)); Value v=rt_get(rt,name); value_list_sort(v); rt_set(rt,name,v); free(name); return; }
|
||||||
if(strcmp(up,"REVERSE")==0){ char *name=xstrdup(trim_inplace(t+7)); Value v=rt_get(rt,name); value_list_reverse(v); rt_set(rt,name,v); free(name); return; }
|
if(strcmp(up,"REVERSE")==0){ char *name=xstrdup(trim_inplace(t+7)); Value v=rt_get(rt,name); value_list_reverse(v); rt_set(rt,name,v); free(name); return; }
|
||||||
if(strcmp(up,"FIND")==0){ const char *in=find_word_ci(t,"IN"), *as=find_word_ci(t,"AS"); if(in&&as){ char *needle=substr(t+4,in); char *listname=substr(in+2,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value nv=eval_expr(rt,needle), lv=eval_expr(rt,listname); int found=0,i; if(lv.type==V_LIST){ for(i=0;i<lv.list->count;i++) if(value_compare(nv,lv.list->items[i])==0){ found=i+1; break; } } rt_set(rt,var,v_num(found)); free(needle); free(listname); free(var); } return; }
|
if(strcmp(up,"FIND")==0){ const char *in=find_word_ci(t,"IN"), *as=find_word_ci(t,"AS"); if(in&&as){ char *needle=substr(t+4,in); char *listname=substr(in+2,as); char *var=xstrdup(trim_inplace((char*)as+2)); Value nv=eval_expr(rt,needle), lv=eval_expr(rt,listname); int found=0,i; if(lv.type==V_LIST){ for(i=0;i<lv.list->count;i++) if(value_compare(nv,lv.list->items[i])==0){ found=i+1; break; } } rt_set(rt,var,v_num(found)); free(needle); free(listname); free(var); } return; }
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Focused HTTP regression checks for marker-safe bodies and bounded responses."""
|
||||||
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
MAX_HTTP_BYTES = 1024 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path == "/marker":
|
||||||
|
body = b"before\n__CLARO_HTTP_STATUS__999\nafter"
|
||||||
|
status = 200
|
||||||
|
elif self.path == "/large":
|
||||||
|
body = b"x" * (MAX_HTTP_BYTES + 1)
|
||||||
|
status = 200
|
||||||
|
else:
|
||||||
|
body = b"not found"
|
||||||
|
status = 404
|
||||||
|
self.send_response(status)
|
||||||
|
self.send_header("Content-Length", str(len(body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(body)
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="claro-http-") as tmp:
|
||||||
|
tmp_path = Path(tmp)
|
||||||
|
script = tmp_path / "http.claro"
|
||||||
|
script.write_text(
|
||||||
|
'HTTP GET URL_MARKER AS body STATUS status\n'
|
||||||
|
'SAY body\n'
|
||||||
|
'SAY status\n'
|
||||||
|
'HTTP GET URL_LARGE AS ignored\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
binary = tmp_path / "claro-http"
|
||||||
|
build = subprocess.run(
|
||||||
|
["gcc", "-std=c99", "-O0", "src/claro.c", "-o", str(binary), "-lm"],
|
||||||
|
cwd=ROOT, text=True, capture_output=True,
|
||||||
|
)
|
||||||
|
if build.returncode:
|
||||||
|
print(build.stderr, end="")
|
||||||
|
return build.returncode
|
||||||
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||||
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
try:
|
||||||
|
marker = f'"http://127.0.0.1:{server.server_port}/marker"'
|
||||||
|
large = f'"http://127.0.0.1:{server.server_port}/large"'
|
||||||
|
text = script.read_text(encoding="utf-8").replace("URL_MARKER", marker).replace("URL_LARGE", large)
|
||||||
|
script.write_text(text, encoding="utf-8")
|
||||||
|
run = subprocess.run([str(binary), str(script)], cwd=ROOT, text=True, capture_output=True)
|
||||||
|
finally:
|
||||||
|
server.shutdown()
|
||||||
|
server.server_close()
|
||||||
|
expected_body = "before\n__CLARO_HTTP_STATUS__999\nafter\n200\n"
|
||||||
|
if run.returncode == 0:
|
||||||
|
print("FAIL: oversized HTTP response was accepted")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
return 1
|
||||||
|
if expected_body not in run.stdout:
|
||||||
|
print("FAIL: marker-like response body was altered")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
if "HTTP response exceeds the safe size limit" not in run.stderr and "HTTP response exceeds the safe size limit" not in run.stdout:
|
||||||
|
print("FAIL: oversized response did not get a beginner-facing diagnostic")
|
||||||
|
print(run.stdout, end="")
|
||||||
|
print(run.stderr, end="")
|
||||||
|
return 1
|
||||||
|
print("PASS: HTTP status is separated from marker-like bodies and responses are bounded")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user