diff --git a/taicpp/Makefile b/taicpp/Makefile index bfa847f..89e0083 100644 --- a/taicpp/Makefile +++ b/taicpp/Makefile @@ -27,7 +27,7 @@ $(BUILD)/main.o: main.cpp $(VERSION_HPP) $(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) -c main.cpp -o $@ $(BIN): $(MAIN_OBJ) $(ALL_OBJS) - $(CXX) $(CXXFLAGS) $(MAIN_OBJ) $(ALL_OBJS) -lsqlite3 -o $@ + $(CXX) $(CXXFLAGS) $(MAIN_OBJ) $(ALL_OBJS) -lsqlite3 -lcurl -o $@ test: $(BIN) $(BUILD)/detz_test $(BUILD)/store_test ./$(BUILD)/detz_test @@ -36,11 +36,11 @@ test: $(BIN) $(BUILD)/detz_test $(BUILD)/store_test $(BUILD)/store_test: tests/store_test.cpp src/store/store.cpp @mkdir -p $(BUILD) - $(CXX) $(CXXFLAGS) -Isrc/store tests/store_test.cpp src/store/store.cpp -lsqlite3 -o $@ + $(CXX) $(CXXFLAGS) -Isrc/store tests/store_test.cpp src/store/store.cpp -lsqlite3 -lcurl -o $@ $(BUILD)/detz_test: tests/test_main.cpp $(ALL_SRCS) $(VERSION_HPP) @mkdir -p $(BUILD) - $(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) tests/test_main.cpp $(ALL_SRCS) -lsqlite3 -o $@ + $(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) tests/test_main.cpp $(ALL_SRCS) -lsqlite3 -lcurl -o $@ lint: $(CXX) $(CXXSTD) $(WARN) -Werror -fsyntax-only main.cpp -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) || true diff --git a/taicpp/main.cpp b/taicpp/main.cpp index 443c7b1..debbf89 100644 --- a/taicpp/main.cpp +++ b/taicpp/main.cpp @@ -1,2 +1,10 @@ -#include "src/cli/cli.h" -int main(int argc, char** argv) { return detz::cli::DetzCli{}.run(argc, argv); } +#include "cli/cli.h" +#include "version.hpp" +#include +#include +int main(int argc, char** argv){ + detz::cli::Parser p; auto cmd = p.parse(argc, argv); + if(cmd.opts.version){ std::puts(std::string(detz::kVersion).c_str()); return 0; } + if(cmd.opts.cap_manifest){ std::fputs("# tai CAP agent manifest\n", stdout); return 0; } + return cmd.run(); +} diff --git a/taicpp/src/util/http.cpp b/taicpp/src/util/http.cpp new file mode 100644 index 0000000..7fbd732 --- /dev/null +++ b/taicpp/src/util/http.cpp @@ -0,0 +1,40 @@ +#include "util/http.h" +#include +#include +#include +namespace detz { +namespace { +struct Buf { std::string data; std::map* hdrs{nullptr}; std::string* ctype{nullptr}; std::string sse; std::function* cb{nullptr}; std::string ev; std::string dt; }; +size_t wbody(char* p, size_t n, size_t m, void* u){ ((Buf*)u)->data.append(p,n*m); return n*m; } +size_t whdr(char* p, size_t n, size_t m, void* u){ Buf* b=(Buf*)u; std::string l(p,n*m); auto c=l.find(':'); if(c!=std::string::npos){ std::string k=l.substr(0,c),v=l.substr(c+1); while(!v.empty()&&(v.back()=='\r'||v.back()=='\n'))v.pop_back(); while(!v.empty()&&v[0]==' ')v.erase(v.begin()); if(b->hdrs)(*b->hdrs)[k]=v; if(k=="Content-Type"&&b->ctype)*b->ctype=v; } return n*m; } +size_t wsse(char* p, size_t n, size_t m, void* u){ Buf* b=(Buf*)u; b->sse.append(p,n*m); size_t pos; while((pos=b->sse.find("\n\n"))!=std::string::npos){ std::string blk=b->sse.substr(0,pos); b->sse.erase(0,pos+2); SseEvent e; std::istringstream s(blk); std::string ln; while(std::getline(s,ln)){ if(!ln.empty()&&ln.back()=='\r')ln.pop_back(); if(ln.rfind("event:",0)==0)e.event=ln.substr(6); else if(ln.rfind("data:",0)==0){ if(!e.data.empty())e.data+="\n"; e.data+=ln.substr(5);} } if(e.event.empty())e.event="message"; if((*b->cb)&&!e.data.empty())(*b->cb)(e); } b->data+=std::string(p,n*m); return n*m; } +} +HttpResponse HttpRequest(const std::string& method, const std::string& url, const std::string& body, const std::map& headers, long timeout_s){ + HttpResponse r; CURL* c=curl_easy_init(); if(!c) return r; Buf b; b.data=""; b.hdrs=&r.headers; b.ctype=&r.content_type; + curl_easy_setopt(c,CURLOPT_URL,url.c_str()); curl_easy_setopt(c,CURLOPT_WRITEFUNCTION,wbody); curl_easy_setopt(c,CURLOPT_WRITEDATA,&b); + curl_easy_setopt(c,CURLOPT_HEADERFUNCTION,whdr); curl_easy_setopt(c,CURLOPT_HEADERDATA,&b); + curl_easy_setopt(c,CURLOPT_TIMEOUT,timeout_s); curl_easy_setopt(c,CURLOPT_FOLLOWLOCATION,1L); + struct curl_slist* hs=nullptr; for(auto&[k,v]:headers) hs=curl_slist_append(hs,(k+": "+v).c_str()); + if(hs) curl_easy_setopt(c,CURLOPT_HTTPHEADER,hs); + if(method=="POST"){ curl_easy_setopt(c,CURLOPT_POST,1L); curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str()); curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size()); } + else if(method!="GET"){ curl_easy_setopt(c,CURLOPT_CUSTOMREQUEST,method.c_str()); if(!body.empty()){curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str());curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size());} } + CURLcode rc=curl_easy_perform(c); if(rc==CURLE_OK){ curl_easy_getinfo(c,CURLINFO_RESPONSE_CODE,&r.status); r.body=b.data; } + curl_slist_free_all(hs); curl_easy_cleanup(c); return r; +} +HttpResponse HttpGet(const std::string& u,const std::map& h,long t){ return HttpRequest("GET",u,"",h,t); } +HttpResponse HttpPost(const std::string& u,const std::string& b,const std::map& h,long t){ return HttpRequest("POST",u,b,h,t); } +long HttpPostSse(const std::string& url,const std::string& body,const std::map& headers,long timeout_s,const std::function& cb){ + CURL* c=curl_easy_init(); if(!c) return 0; Buf b; b.cb=(std::function*)&cb; + curl_easy_setopt(c,CURLOPT_URL,url.c_str()); curl_easy_setopt(c,CURLOPT_POST,1L); + curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str()); curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size()); + curl_easy_setopt(c,CURLOPT_WRITEFUNCTION,wsse); curl_easy_setopt(c,CURLOPT_WRITEDATA,&b); + curl_easy_setopt(c,CURLOPT_TIMEOUT,timeout_s); + struct curl_slist* hs=nullptr; for(auto&[k,v]:headers) hs=curl_slist_append(hs,(k+": "+v).c_str()); + if(hs) curl_easy_setopt(c,CURLOPT_HTTPHEADER,hs); + long st=0; if(curl_easy_perform(c)==CURLE_OK) curl_easy_getinfo(c,CURLINFO_RESPONSE_CODE,&st); + curl_slist_free_all(hs); curl_easy_cleanup(c); return st; +} +std::string JsonEscape(const std::string& s){ std::string o; for(char c:s){ switch(c){case '"':o+="\\\"";break;case '\\':o+="\\\\";break;case '\n':o+="\\n";break;case '\r':o+="\\r";break;case '\t':o+="\\t";break;default: if((unsigned char)c<0x20){char b[8];snprintf(b,8,"\\u%04x",c);o+=b;} else o+=c;} } return o; } +std::string JsonGetStr(const std::string& o,const std::string& k){ std::string p="\""+k+"\""; auto i=o.find(p); if(i==std::string::npos)return ""; i=o.find(':',i); if(i==std::string::npos)return ""; ++i; while(i +#include +#include +#include +namespace detz { +// Minimal HTTP client over libcurl (mirrors tai.py urllib flows: GET/POST, +// headers, timeouts, SSE streaming). No dependency beyond libcurl. +struct HttpResponse { long status{0}; std::string body; std::map headers; std::string content_type; }; +struct SseEvent { std::string event; std::string data; }; +HttpResponse HttpGet(const std::string& url, const std::map& headers = {}, long timeout_s = 60); +HttpResponse HttpPost(const std::string& url, const std::string& body, const std::map& headers = {}, long timeout_s = 60); +HttpResponse HttpRequest(const std::string& method, const std::string& url, const std::string& body, const std::map& headers, long timeout_s); +// Stream SSE: invokes cb per event, returns final HTTP status. +long HttpPostSse(const std::string& url, const std::string& body, const std::map& headers, long timeout_s, const std::function& cb); +// Tiny JSON helpers (escape/extract) mirroring tai.py json usage. +std::string JsonEscape(const std::string& s); +std::string JsonGetStr(const std::string& obj, const std::string& key); +long JsonGetInt(const std::string& obj, const std::string& key, long dflt = 0); +}