244 lines
4.8 KiB
C
244 lines
4.8 KiB
C
#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;
|
|
}
|
|
|