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
jfree(CEntry * ce)17 jfree(CEntry *ce)
18 {
19 JEntry *j;
20
21 j = (JEntry*)ce;
22 jclose(j->reply);
23 }
24
25 static JEntry*
jcachelookup(char * request)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
jcacheflush(char * substr)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*
makehttprequest(char * host,char * path,char * postdata)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*
makerequest(char * method,char * name1,va_list arg)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*
dojsonhttp(Protocol * proto,char * host,char * request,int rfd,vlong rlength)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*
jsonrpc(Protocol * proto,char * host,char * path,char * method,char * name1,va_list arg,int usecache)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*
ncsmug(char * method,char * name1,...)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*
smug(char * method,char * name1,...)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*
jsonupload(Protocol * proto,char * host,char * req,int rfd,vlong rlength)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 }
244