aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/smugfs/jsonrpc.c
diff options
context:
space:
mode:
authorRuss Cox <rsc@swtch.com>2008-08-03 07:42:27 -0700
committerRuss Cox <rsc@swtch.com>2008-08-03 07:42:27 -0700
commit18824b586835525594cde126fbc90b8281d5af8b (patch)
treee8b69c05eda4543b6d2f3d30777abe6109b48b7f /src/cmd/smugfs/jsonrpc.c
parent3d36f4437348227c5bad62587dc12b5fd4a3e95e (diff)
downloadplan9port-18824b586835525594cde126fbc90b8281d5af8b.tar.gz
plan9port-18824b586835525594cde126fbc90b8281d5af8b.tar.bz2
plan9port-18824b586835525594cde126fbc90b8281d5af8b.zip
smugfs(4): new program
Diffstat (limited to 'src/cmd/smugfs/jsonrpc.c')
-rw-r--r--src/cmd/smugfs/jsonrpc.c244
1 files changed, 244 insertions, 0 deletions
diff --git a/src/cmd/smugfs/jsonrpc.c b/src/cmd/smugfs/jsonrpc.c
new file mode 100644
index 00000000..92490e77
--- /dev/null
+++ b/src/cmd/smugfs/jsonrpc.c
@@ -0,0 +1,244 @@
+#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;
+}
+