blob: 92490e775a5e696bf24ceaa7969f76e603872ac0 [file] [log] [blame]
#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;
}