| #include "a.h" |
| |
| // JSON request/reply cache. |
| |
| int chattyhttp; |
| |
| typedef struct JEntry JEntry; |
| struct JEntry |
| { |
| CEntry ce; |
| Json *reply; |
| }; |
| |
| static Cache *jsoncache; |
| |
| static void |
| jfree(CEntry *ce) |
| { |
| JEntry *j; |
| |
| j = (JEntry*)ce; |
| jclose(j->reply); |
| } |
| |
| static JEntry* |
| jcachelookup(char *request) |
| { |
| if(jsoncache == nil) |
| jsoncache = newcache(sizeof(JEntry), 1000, jfree); |
| return (JEntry*)cachelookup(jsoncache, request, 1); |
| } |
| |
| void |
| jcacheflush(char *substr) |
| { |
| if(jsoncache == nil) |
| return; |
| cacheflush(jsoncache, substr); |
| } |
| |
| |
| // JSON RPC over HTTP |
| |
| static char* |
| makehttprequest(char *host, char *path, char *postdata) |
| { |
| Fmt fmt; |
| |
| fmtstrinit(&fmt); |
| fmtprint(&fmt, "POST %s HTTP/1.0\r\n", path); |
| fmtprint(&fmt, "Host: %s\r\n", host); |
| fmtprint(&fmt, "User-Agent: " USER_AGENT "\r\n"); |
| fmtprint(&fmt, "Content-Type: application/x-www-form-urlencoded\r\n"); |
| fmtprint(&fmt, "Content-Length: %d\r\n", strlen(postdata)); |
| fmtprint(&fmt, "\r\n"); |
| fmtprint(&fmt, "%s", postdata); |
| return fmtstrflush(&fmt); |
| } |
| |
| static char* |
| makerequest(char *method, char *name1, va_list arg) |
| { |
| char *p, *key, *val; |
| Fmt fmt; |
| |
| fmtstrinit(&fmt); |
| fmtprint(&fmt, "&"); |
| p = name1; |
| while(p != nil){ |
| key = p; |
| val = va_arg(arg, char*); |
| if(val == nil) |
| sysfatal("jsonrpc: nil value"); |
| fmtprint(&fmt, "%U=%U&", key, val); |
| p = va_arg(arg, char*); |
| } |
| // TODO: These are SmugMug-specific, probably. |
| fmtprint(&fmt, "method=%s&", method); |
| if(sessid) |
| fmtprint(&fmt, "SessionID=%s&", sessid); |
| fmtprint(&fmt, "APIKey=%s", APIKEY); |
| return fmtstrflush(&fmt); |
| } |
| |
| static char* |
| dojsonhttp(Protocol *proto, char *host, char *request, int rfd, vlong rlength) |
| { |
| char *data; |
| HTTPHeader hdr; |
| |
| data = httpreq(proto, host, request, &hdr, rfd, rlength); |
| if(data == nil){ |
| fprint(2, "httpreq: %r\n"); |
| return nil; |
| } |
| if(strcmp(hdr.contenttype, "application/json") != 0 && |
| (strcmp(hdr.contenttype, "text/html; charset=utf-8") != 0 || data[0] != '{')){ // upload.smugmug.com, sigh |
| werrstr("bad content type: %s", hdr.contenttype); |
| fprint(2, "Content-Type: %s\n", hdr.contenttype); |
| write(2, data, hdr.contentlength); |
| return nil; |
| } |
| if(hdr.contentlength == 0){ |
| werrstr("no content"); |
| return nil; |
| } |
| return data; |
| } |
| |
| Json* |
| jsonrpc(Protocol *proto, char *host, char *path, char *method, char *name1, va_list arg, int usecache) |
| { |
| char *httpreq, *request, *reply; |
| JEntry *je; |
| Json *jv, *jstat, *jmsg; |
| |
| request = makerequest(method, name1, arg); |
| |
| je = nil; |
| if(usecache){ |
| je = jcachelookup(request); |
| if(je->reply){ |
| free(request); |
| return jincref(je->reply); |
| } |
| } |
| |
| rpclog("%T %s", request); |
| httpreq = makehttprequest(host, path, request); |
| free(request); |
| |
| if((reply = dojsonhttp(proto, host, httpreq, -1, 0)) == nil){ |
| free(httpreq); |
| return nil; |
| } |
| free(httpreq); |
| |
| jv = parsejson(reply); |
| free(reply); |
| if(jv == nil){ |
| rpclog("%s: error parsing JSON reply: %r", method); |
| return nil; |
| } |
| |
| if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0){ |
| if(je) |
| je->reply = jincref(jv); |
| return jv; |
| } |
| |
| if(jstrcmp(jstat, "fail") == 0){ |
| jmsg = jlookup(jv, "message"); |
| if(jmsg){ |
| // If there are no images, that's not an error! |
| // (But SmugMug says it is.) |
| if(strcmp(method, "smugmug.images.get") == 0 && |
| jstrcmp(jmsg, "empty set - no images found") == 0){ |
| jclose(jv); |
| jv = parsejson("{\"stat\":\"ok\", \"Images\":[]}"); |
| if(jv == nil) |
| sysfatal("parsejson: %r"); |
| je->reply = jincref(jv); |
| return jv; |
| } |
| if(printerrors) |
| fprint(2, "%s: %J\n", method, jv); |
| rpclog("%s: %J", method, jmsg); |
| werrstr("%J", jmsg); |
| jclose(jv); |
| return nil; |
| } |
| rpclog("%s: json status: %J", method, jstat); |
| jclose(jv); |
| return nil; |
| } |
| |
| rpclog("%s: json stat=%J", method, jstat); |
| jclose(jv); |
| return nil; |
| } |
| |
| Json* |
| ncsmug(char *method, char *name1, ...) |
| { |
| Json *jv; |
| va_list arg; |
| |
| va_start(arg, name1); |
| // TODO: Could use https only for login. |
| jv = jsonrpc(&https, HOST, PATH, method, name1, arg, 0); |
| va_end(arg); |
| rpclog("reply: %J", jv); |
| return jv; |
| } |
| |
| Json* |
| smug(char *method, char *name1, ...) |
| { |
| Json *jv; |
| va_list arg; |
| |
| va_start(arg, name1); |
| jv = jsonrpc(&http, HOST, PATH, method, name1, arg, 1); |
| va_end(arg); |
| return jv; |
| } |
| |
| Json* |
| jsonupload(Protocol *proto, char *host, char *req, int rfd, vlong rlength) |
| { |
| Json *jv, *jstat, *jmsg; |
| char *reply; |
| |
| if((reply = dojsonhttp(proto, host, req, rfd, rlength)) == nil) |
| return nil; |
| |
| jv = parsejson(reply); |
| free(reply); |
| if(jv == nil){ |
| fprint(2, "upload: error parsing JSON reply\n"); |
| return nil; |
| } |
| |
| if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0) |
| return jv; |
| |
| if(jstrcmp(jstat, "fail") == 0){ |
| jmsg = jlookup(jv, "message"); |
| if(jmsg){ |
| fprint(2, "upload: %J\n", jmsg); |
| werrstr("%J", jmsg); |
| jclose(jv); |
| return nil; |
| } |
| fprint(2, "upload: json status: %J\n", jstat); |
| jclose(jv); |
| return nil; |
| } |
| |
| fprint(2, "upload: %J\n", jv); |
| jclose(jv); |
| return nil; |
| } |
| |