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