Blob


1 #include "a.h"
3 // JSON request/reply cache.
5 int chattyhttp;
7 typedef struct JEntry JEntry;
8 struct JEntry
9 {
10 CEntry ce;
11 Json *reply;
12 };
14 static Cache *jsoncache;
16 static void
17 jfree(CEntry *ce)
18 {
19 JEntry *j;
21 j = (JEntry*)ce;
22 jclose(j->reply);
23 }
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 }
33 void
34 jcacheflush(char *substr)
35 {
36 if(jsoncache == nil)
37 return;
38 cacheflush(jsoncache, substr);
39 }
42 // JSON RPC over HTTP
44 static char*
45 makehttprequest(char *host, char *path, char *postdata)
46 {
47 Fmt fmt;
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 }
60 static char*
61 makerequest(char *method, char *name1, va_list arg)
62 {
63 char *p, *key, *val;
64 Fmt fmt;
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 }
85 static char*
86 dojsonhttp(Protocol *proto, char *host, char *request, int rfd, vlong rlength)
87 {
88 char *data;
89 HTTPHeader hdr;
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;
103 if(hdr.contentlength == 0){
104 werrstr("no content");
105 return nil;
107 return data;
110 Json*
111 jsonrpc(Protocol *proto, char *host, char *path, char *method, char *name1, va_list arg, int usecache)
113 char *httpreq, *request, *reply;
114 JEntry *je;
115 Json *jv, *jstat, *jmsg;
117 request = makerequest(method, name1, arg);
119 je = nil;
120 if(usecache){
121 je = jcachelookup(request);
122 if(je->reply){
123 free(request);
124 return jincref(je->reply);
128 rpclog("%T %s", request);
129 httpreq = makehttprequest(host, path, request);
130 free(request);
132 if((reply = dojsonhttp(proto, host, httpreq, -1, 0)) == nil){
133 free(httpreq);
134 return nil;
136 free(httpreq);
138 jv = parsejson(reply);
139 free(reply);
140 if(jv == nil){
141 rpclog("%s: error parsing JSON reply: %r", method);
142 return nil;
145 if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0){
146 if(je)
147 je->reply = jincref(jv);
148 return jv;
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;
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;
172 rpclog("%s: json status: %J", method, jstat);
173 jclose(jv);
174 return nil;
177 rpclog("%s: json stat=%J", method, jstat);
178 jclose(jv);
179 return nil;
182 Json*
183 ncsmug(char *method, char *name1, ...)
185 Json *jv;
186 va_list arg;
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;
196 Json*
197 smug(char *method, char *name1, ...)
199 Json *jv;
200 va_list arg;
202 va_start(arg, name1);
203 jv = jsonrpc(&http, HOST, PATH, method, name1, arg, 1);
204 va_end(arg);
205 return jv;
208 Json*
209 jsonupload(Protocol *proto, char *host, char *req, int rfd, vlong rlength)
211 Json *jv, *jstat, *jmsg;
212 char *reply;
214 if((reply = dojsonhttp(proto, host, req, rfd, rlength)) == nil)
215 return nil;
217 jv = parsejson(reply);
218 free(reply);
219 if(jv == nil){
220 fprint(2, "upload: error parsing JSON reply\n");
221 return nil;
224 if(jstrcmp((jstat = jlookup(jv, "stat")), "ok") == 0)
225 return jv;
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;
235 fprint(2, "upload: json status: %J\n", jstat);
236 jclose(jv);
237 return nil;
240 fprint(2, "upload: %J\n", jv);
241 jclose(jv);
242 return nil;