|  | #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; | 
|  | } | 
|  |  |