jsonrpc.c (4900B)
1 #include "a.h" 2 3 // JSON request/reply cache. 4 5 int chattyhttp; 6 7 typedef struct JEntry JEntry; 8 struct JEntry 9 { 10 CEntry ce; 11 Json *reply; 12 }; 13 14 static Cache *jsoncache; 15 16 static void 17 jfree(CEntry *ce) 18 { 19 JEntry *j; 20 21 j = (JEntry*)ce; 22 jclose(j->reply); 23 } 24 25 static JEntry* 26 jcachelookup(char *request) 27 { 28 if(jsoncache == nil) 29 jsoncache = newcache(sizeof(JEntry), 1000, jfree); 30 return (JEntry*)cachelookup(jsoncache, request, 1); 31 } 32 33 void 34 jcacheflush(char *substr) 35 { 36 if(jsoncache == nil) 37 return; 38 cacheflush(jsoncache, substr); 39 } 40 41 42 // JSON RPC over HTTP 43 44 static char* 45 makehttprequest(char *host, char *path, char *postdata) 46 { 47 Fmt fmt; 48 49 fmtstrinit(&fmt); 50 fmtprint(&fmt, "POST %s HTTP/1.0\r\n", path); 51 fmtprint(&fmt, "Host: %s\r\n", host); 52 fmtprint(&fmt, "User-Agent: " USER_AGENT "\r\n"); 53 fmtprint(&fmt, "Content-Type: application/x-www-form-urlencoded\r\n"); 54 fmtprint(&fmt, "Content-Length: %d\r\n", strlen(postdata)); 55 fmtprint(&fmt, "\r\n"); 56 fmtprint(&fmt, "%s", postdata); 57 return fmtstrflush(&fmt); 58 } 59 60 static char* 61 makerequest(char *method, char *name1, va_list arg) 62 { 63 char *p, *key, *val; 64 Fmt fmt; 65 66 fmtstrinit(&fmt); 67 fmtprint(&fmt, "&"); 68 p = name1; 69 while(p != nil){ 70 key = p; 71 val = va_arg(arg, char*); 72 if(val == nil) 73 sysfatal("jsonrpc: nil value"); 74 fmtprint(&fmt, "%U=%U&", key, val); 75 p = va_arg(arg, char*); 76 } 77 // TODO: These are SmugMug-specific, probably. 78 fmtprint(&fmt, "method=%s&", method); 79 if(sessid) 80 fmtprint(&fmt, "SessionID=%s&", sessid); 81 fmtprint(&fmt, "APIKey=%s", APIKEY); 82 return fmtstrflush(&fmt); 83 } 84 85 static char* 86 dojsonhttp(Protocol *proto, char *host, char *request, int rfd, vlong rlength) 87 { 88 char *data; 89 HTTPHeader hdr; 90 91 data = httpreq(proto, host, request, &hdr, rfd, rlength); 92 if(data == nil){ 93 fprint(2, "httpreq: %r\n"); 94 return nil; 95 } 96 if(strcmp(hdr.contenttype, "application/json") != 0 && 97 (strcmp(hdr.contenttype, "text/html; charset=utf-8") != 0 || data[0] != '{')){ // upload.smugmug.com, sigh 98 werrstr("bad content type: %s", hdr.contenttype); 99 fprint(2, "Content-Type: %s\n", hdr.contenttype); 100 write(2, data, hdr.contentlength); 101 return nil; 102 } 103 if(hdr.contentlength == 0){ 104 werrstr("no content"); 105 return nil; 106 } 107 return data; 108 } 109 110 Json* 111 jsonrpc(Protocol *proto, char *host, char *path, char *method, char *name1, va_list arg, int usecache) 112 { 113 char *httpreq, *request, *reply; 114 JEntry *je; 115 Json *jv, *jstat, *jmsg; 116 117 request = makerequest(method, name1, arg); 118 119 je = nil; 120 if(usecache){ 121 je = jcachelookup(request); 122 if(je->reply){ 123 free(request); 124 return jincref(je->reply); 125 } 126 } 127 128 rpclog("%T %s", request); 129 httpreq = makehttprequest(host, path, request); 130 free(request); 131 132 if((reply = dojsonhttp(proto, host, httpreq, -1, 0)) == nil){ 133 free(httpreq); 134 return nil; 135 } 136 free(httpreq); 137 138 jv = parsejson(reply); 139 free(reply); 140 if(jv == nil){ 141 rpclog("%s: error parsing JSON reply: %r", method); 142 return nil; 143 } 144 145 if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0){ 146 if(je) 147 je->reply = jincref(jv); 148 return jv; 149 } 150 151 if(jstrcmp(jstat, "fail") == 0){ 152 jmsg = jlookup(jv, "message"); 153 if(jmsg){ 154 // If there are no images, that's not an error! 155 // (But SmugMug says it is.) 156 if(strcmp(method, "smugmug.images.get") == 0 && 157 jstrcmp(jmsg, "empty set - no images found") == 0){ 158 jclose(jv); 159 jv = parsejson("{\"stat\":\"ok\", \"Images\":[]}"); 160 if(jv == nil) 161 sysfatal("parsejson: %r"); 162 je->reply = jincref(jv); 163 return jv; 164 } 165 if(printerrors) 166 fprint(2, "%s: %J\n", method, jv); 167 rpclog("%s: %J", method, jmsg); 168 werrstr("%J", jmsg); 169 jclose(jv); 170 return nil; 171 } 172 rpclog("%s: json status: %J", method, jstat); 173 jclose(jv); 174 return nil; 175 } 176 177 rpclog("%s: json stat=%J", method, jstat); 178 jclose(jv); 179 return nil; 180 } 181 182 Json* 183 ncsmug(char *method, char *name1, ...) 184 { 185 Json *jv; 186 va_list arg; 187 188 va_start(arg, name1); 189 // TODO: Could use https only for login. 190 jv = jsonrpc(&https, HOST, PATH, method, name1, arg, 0); 191 va_end(arg); 192 rpclog("reply: %J", jv); 193 return jv; 194 } 195 196 Json* 197 smug(char *method, char *name1, ...) 198 { 199 Json *jv; 200 va_list arg; 201 202 va_start(arg, name1); 203 jv = jsonrpc(&http, HOST, PATH, method, name1, arg, 1); 204 va_end(arg); 205 return jv; 206 } 207 208 Json* 209 jsonupload(Protocol *proto, char *host, char *req, int rfd, vlong rlength) 210 { 211 Json *jv, *jstat, *jmsg; 212 char *reply; 213 214 if((reply = dojsonhttp(proto, host, req, rfd, rlength)) == nil) 215 return nil; 216 217 jv = parsejson(reply); 218 free(reply); 219 if(jv == nil){ 220 fprint(2, "upload: error parsing JSON reply\n"); 221 return nil; 222 } 223 224 if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0) 225 return jv; 226 227 if(jstrcmp(jstat, "fail") == 0){ 228 jmsg = jlookup(jv, "message"); 229 if(jmsg){ 230 fprint(2, "upload: %J\n", jmsg); 231 werrstr("%J", jmsg); 232 jclose(jv); 233 return nil; 234 } 235 fprint(2, "upload: json status: %J\n", jstat); 236 jclose(jv); 237 return nil; 238 } 239 240 fprint(2, "upload: %J\n", jv); 241 jclose(jv); 242 return nil; 243 }