diff --git a/world/drivers/ldmud/library/library.c b/world/drivers/ldmud/library/library.c index e892706..8f08186 100644 --- a/world/drivers/ldmud/library/library.c +++ b/world/drivers/ldmud/library/library.c @@ -19,6 +19,7 @@ inherit PRO_PATH "http/library2"; inherit NET_PATH "library/sandbox"; # endif inherit NET_PATH "library/base64"; +inherit NET_PATH "library/hmac"; inherit NET_PATH "library/dns"; inherit NET_PATH "library/htbasics"; inherit NET_PATH "library/json"; diff --git a/world/net/http/fetch.c b/world/net/http/fetch.c index cd5b1a1..e24be74 100644 --- a/world/net/http/fetch.c +++ b/world/net/http/fetch.c @@ -13,6 +13,7 @@ #include #include #include +#include virtual inherit NET_PATH "output"; // virtual: in case we get inherited.. inherit NET_PATH "connect"; @@ -25,11 +26,10 @@ inherit NET_PATH "queue"; #endif volatile mapping headers, fheaders; -volatile string modificationtime, etag, http_message; -volatile string useragent = SERVER_VERSION; +volatile mapping rheaders = (["User-Agent": SERVER_VERSION]); +volatile string http_message; volatile int http_status, port, fetching, ssl; -volatile string buffer, thehost, url, fetched, host, resource; -volatile string basicauth = ""; +volatile string buffer, thehost, url, fetched, host, resource, method; int parse_status(string all); int parse_header(string all); @@ -37,7 +37,9 @@ int buffer_content(string all); string qHost() { return thehost; } -void fetch(string murl) { +varargs void fetch(string murl, string meth, mapping hdrs) { + method = meth || "GET"; + if (hdrs) rheaders += hdrs; if (url != murl) { // accept.c does this for us: //url = replace(murl, ":/", "://"); @@ -55,11 +57,10 @@ void fetch(string murl) { object load() { return ME; } void sAuth(string user, string password) { - basicauth = "Authorization: Basic "+ - encode_base64(user +":"+ password) +"\r\n"; + rheaders["Authorization"] = "Basic " + encode_base64(user +":"+ password); } -string sAgent(string a) { return useragent = a; } +string sAgent(string a) { return rheaders["User-Agent"] = a; } // net/place/news code follows. @@ -95,17 +96,15 @@ varargs int real_logon(int failure) { unless (url) return -3; unless (resource) sscanf(url, "%s://%s/%s", scheme, host, resource); - buffer = basicauth; - if (modificationtime) - buffer += "If-Modified-Since: "+ modificationtime + "\r\n"; - if (useragent) buffer += "User-Agent: "+ useragent +"\r\n"; - //if (etag) - // emit("If-None-Match: " + etag + "\r\n"); + buffer = ""; + foreach (string key, string value : rheaders) { + buffer += key + ": " + value + "\r\n"; + } // we won't need connection: close w/ http/1.0 //emit("Connection: close\r\n\r\n"); P2(("%O fetching /%s from %O\n", ME, resource, host)) P4(("%O using %O\n", ME, buffer)) - emit("GET /"+ resource +" HTTP/1.0\r\n" + emit(method + " /"+ resource +" HTTP/1.0\r\n" "Host: "+ host +"\r\n" + buffer + "\r\n"); @@ -178,9 +177,9 @@ disconnected(remainder) { P2(("%O got disconnected.. %O\n", ME, remainder)) headers["_fetchtime"] = isotime(ctime(time()), 1); if (headers["last-modified"]) - modificationtime = headers["last-modified"]; - if (headers["etag"]) - etag = headers["etag"]; // heise does not work with etag + rheaders["If-Modified-Since"] = headers["last-modified"]; + //if (headers["etag"]) + // rheaders["If-None-Match"] = headers["etag"]; // heise does not work with etag fetched = buffer; if (remainder) fetched += remainder; @@ -228,12 +227,25 @@ string qHeader(mixed key) { return 0; } +string qReqHeader(string key) { + return rheaders[key]; +} + +void sReqHeader(string key, string value) { + rheaders[key] = value; +} + varargs void refetch(closure cb, int willbehave) { enqueue(ME, ({ cb, willbehave })); unless (fetching) connect(); } -protected create() { +void reset() { + fetched = 0; qCreate(); qInit(ME, 150, 5); } + +protected create() { + reset(); +} diff --git a/world/net/http/library.i b/world/net/http/library.i index 195b295..24344cf 100644 --- a/world/net/http/library.i +++ b/world/net/http/library.i @@ -134,6 +134,20 @@ string urldecode(string txt) { return txt; } +static string xx2c(string xx) { + string c = " "; + c[0] = hex2int(xx); + return c; +} + +static string c2xx(string c) { + return "%" + upper_case(sprintf("%x", c[0])); +} + +string urlencode(string txt) { + return regreplace(txt, "[^A-Za-z0-9._~-]", #'c2xx, 1); +} + #ifndef DEFAULT_HT_TYPE # define DEFAULT_HT_TYPE "text/plain" #endif @@ -177,3 +191,19 @@ default: } return 0; } + +parse_query(query, qs) { + foreach (string pair : explode(qs, "&")) { + string key, val; + + if (sscanf(pair, "%s=%s", key, val)) { + P3(("query: pair: %s, %s\n", urldecode(key), + urldecode(val))) + query[urldecode(key)] = urldecode(val); + } else { + P3(("query: single: %s\n", urldecode(pair))) + query[urldecode(pair)] = 1; + } + } + return query; +} diff --git a/world/net/http/oauth.c b/world/net/http/oauth.c new file mode 100644 index 0000000..b5992c3 --- /dev/null +++ b/world/net/http/oauth.c @@ -0,0 +1,101 @@ +/* + current specification: + http://tools.ietf.org/html/draft-hammer-oauth-10 + + older ones: + http://oauth.net/core/1.0a/ + http://oauth.net/core/1.0/ +*/ + +#include +#include + +string consumer_key; +string consumer_secret; +string request_token_url; +string request_token; +string request_secret; +string access_token_url; +string access_token; +string access_secret; +string authorize_url; +string callback_url = "http://" + my_lower_case_host() + ":" + HTTP_PORT + "/oauth"; //TODO: https? +mapping oauth = ([]); +object user; + +varargs void fetch(object ua, string url, string method, mapping oauth) { + P3((">> oauth:fetch(%O, %O, %O)\n", url, method, oauth)) + unless (method) method = "GET"; + unless (oauth) oauth = ([]); + + oauth["consumer_key"] = consumer_key; + if (access_token || request_token) oauth["token"] = access_token || request_token; + string token_secret = access_token ? access_secret : request_token ? request_secret : ""; + oauth["timestamp"] = time(); + oauth["nonce"] = sprintf("%x", random(oauth["timestamp"] ^ 98987)); + oauth["signature_method"] = "HMAC-SHA1"; + oauth["version"] = "1.0"; + + array(string) params = ({}); + foreach (string key : sort_array(m_indices(oauth), #'>)) //')) + params += ({"oauth_" + key + "=" + urlencode(to_string(oauth[key]))}); + string base_str = method + "&" + urlencode(url) + "&" + urlencode(implode(params, "&")); + oauth["signature"] = hmac_base64(TLS_HASH_SHA1, urlencode(consumer_secret) + "&" + urlencode(token_secret), base_str); + + params = ({}); + foreach (string key, string value : oauth) + params += ({"oauth_" + key + "=\"" + urlencode(to_string(value)) + "\""}); + + ua->fetch(url, method, (["Authorization": "OAuth " + implode(params, ",")])); +} + +void parse_request_token(string body, mapping headers) { + P3((">> oauth:parse_request_token(%O, %O)\n", body, headers)) + mapping params = ([]); + parse_query(params, body); + request_token = params["oauth_token"]; + request_secret = params["oauth_token_secret"]; + if (strlen(request_token) && strlen(request_secret)) { + shared_memory("oauth_request_tokens")[request_token] = ME; + sendmsg(user, "_notice_oauth_authorize_url", "Open [_url] to perform authorization.", + (["_url": authorize_url + "?oauth_token=" + request_token])); + } else { + sendmsg(user, "_error_oauth_token_request", "OAuth failed: could not get a request token."); + } +} + +void parse_access_token(string body, mapping headers) { + P3((">> oauth:parse_access_token(%O, %O)\n", body, headers)) + mapping params = ([]); + parse_query(params, body); + access_token = params["oauth_token"]; + access_secret = params["oauth_token_secret"]; + if (strlen(access_token) && strlen(access_secret)) { + sendmsg(user, "_notice_oauth_success", "OAuth successful."); + } else { + sendmsg(user, "_error_oauth_token_access", "OAuth failed: could not get an access token."); + } +} + +void verified(string verifier) { + P3((">> oauth:verified(%O)\n", verifier)) + object ua = clone_object(NET_PATH "http/fetch"); + ua->content(#'parse_access_token, 1, 1); //'); + fetch(ua, access_token_url, "POST", (["verifier": verifier])); +} + +object load(object usr, string key, string secret, string request, string access, string authorize) { + if (usr) user = usr; + if (key) consumer_key = key; + if (secret) consumer_secret = secret; + if (request) request_token_url = request; + if (access) access_token_url = access; + if (authorize) authorize_url = authorize; + + if (request_token_url && user) { + object ua = clone_object(NET_PATH "http/fetch"); + ua->content(#'parse_request_token, 1, 1); //'); + fetch(ua, request_token_url, "POST", (["callback": callback_url])); + } + return ME; +} diff --git a/world/net/http/server.c b/world/net/http/server.c index 5960d6a..30affb7 100644 --- a/world/net/http/server.c +++ b/world/net/http/server.c @@ -100,22 +100,6 @@ parse_header(input) { } } -parse_query(query, qs) { - foreach (string pair : explode(qs, "&")) { - string key, val; - - if (sscanf(pair, "%s=%s", key, val)) { - P3(("query: pair: %s, %s\n", urldecode(key), - urldecode(val))) - query[urldecode(key)] = urldecode(val); - } else { - P3(("query: single: %s\n", urldecode(pair))) - query[urldecode(pair)] = 1; - } - } - return query; -} - process() { string t, ext; mapping query = ([]); @@ -192,6 +176,15 @@ case "/static": // really don't like to do this, but the IE stores directories case "/static/": file = "/static/index.html"; break; +case "/oauth": + object oauth; + //PT((">>> shm: %O\n", shared_memory("oauth_request_tokens"))) + if (query["oauth_verifier"] && (oauth = shared_memory("oauth_request_tokens")[query["oauth_token"]])) { + //PT((">>> oauth: %O\n", oauth)) + oauth->verified(query["oauth_verifier"]); + m_delete(shared_memory("oauth_request_tokens"), query["oauth_token"]); + } + return 1; } switch (file[1]) { case '~': diff --git a/world/net/library/hmac.c b/world/net/library/hmac.c new file mode 100644 index 0000000..b319439 --- /dev/null +++ b/world/net/library/hmac.c @@ -0,0 +1,13 @@ +#include + +inherit NET_PATH "library/base64"; + +#include HTTP_PATH "library.i" + +string hmac_bin(int method, string key, string arg) { + return regreplace(hmac(method, key, arg), "..", #'xx2c, 1); //' +} + +string hmac_base64(int method, string key, string arg) { + return encode_base64((int *)hmac_bin(method, key, arg)); +} diff --git a/world/net/library/share.c b/world/net/library/share.c index 195be99..8e1a340 100644 --- a/world/net/library/share.c +++ b/world/net/library/share.c @@ -171,6 +171,7 @@ volatile mapping share = ([ "_understand_modules" : PSYC_ROUTING, "_using_modules" : PSYC_ROUTING, ]), + "oauth_request_tokens": ([ ]), ]); varargs mixed shared_memory(mixed datensatz, mixed value) { diff --git a/world/net/library/signature.c b/world/net/library/signature.c index 091ff0b..4660024 100644 --- a/world/net/library/signature.c +++ b/world/net/library/signature.c @@ -78,6 +78,8 @@ private volatile mapping _sigs = ([ "_request_remove": ({ "_request_remove", 0, "_person" }), "_request_priv": ({ "_request_privacy", 0, "_privacy" }), "_request_privacy": ({ "_request_privacy", 0, "_privacy" }), + "_request_tw": ({ "_request_twitter", 0, "_switch" }), + "_request_twitter": ({ "_request_twitter", 0, "_switch" }), #endif #ifdef EXPERIMENTAL // stuff to play around with diff --git a/world/net/place/userthreads.c b/world/net/place/userthreads.c index e4aab38..4e997d5 100644 --- a/world/net/place/userthreads.c +++ b/world/net/place/userthreads.c @@ -16,6 +16,7 @@ volatile mixed lastTry; volatile string owner; volatile string channel; +volatile object twitter; load(name, keep) { P3((">> userthreads:load(%O, %O)\n", name, keep)) @@ -23,6 +24,7 @@ load(name, keep) { sscanf(name, "~%s#%s", owner, channel); vSet("owners", ([ owner: 0 ])); vSet("privacy", "private"); + vSet("twitter", 0); vSet("_restrict_invitation", BLAME); vSet("_filter_conversation", BLAME); @@ -52,6 +54,7 @@ enter(source, mc, data, vars) { if (p == source) { p->sChannel(MYNICK); + if (v("twitter") && !twitter) twitter = clone_object(NET_PATH "twitter/client")->load(source); } return ::enter(source, mc, data, vars); @@ -125,6 +128,23 @@ _request_privacy(source, mc, data, vars, b) { return 1; } +_request_twitter(source, mc, data, vars, b) { + string sw = vars["_switch"]; + if (sw == "on" || sw == "enabled" || sw == "1") { + unless (twitter) twitter = clone_object(NET_PATH "twitter/client")->load(source); + vSet("twitter", 1); + save(); + } else if (sw == "off" || sw == "disabled" || sw == "0") { + if (twitter) twitter = 0; + vSet("twitter", 0); + save(); + } + DT(else if (sw == "test") twitter->home();) + + sendmsg(source, "_status_twitter", "Twitter submission is [_status].", (["_status": v("twitter") ? "enabled" : "disabled"])); + return 1; +} + htMain(int last) { return htmlEntries(_thread, last, 1, channel); } diff --git a/world/net/twitter/client.c b/world/net/twitter/client.c new file mode 100644 index 0000000..3cd84e5 --- /dev/null +++ b/world/net/twitter/client.c @@ -0,0 +1,23 @@ +#include + +inherit NET_PATH "http/oauth"; + +object load(object usr, string key, string secret, string request, string access, string authorize) { + consumer_key = TWITTER_KEY; + consumer_secret = TWITTER_SECRET; + request_token_url = "http://twitter.com/oauth/request_token"; + access_token_url = "http://twitter.com/oauth/access_token"; + authorize_url = "http://twitter.com/oauth/authorize"; + + return ::load(usr, key, secret, request, access, authorize); +} + +void parse_home(string body, string headers) { + P3(("twitter/client:parse_home(%O, %O)\n", body, headers)) +} + +void home() { + object ua = clone_object(NET_PATH "http/fetch"); + ua->content(#'parse_home, 1, 1); //'); + fetch(ua, "http://api.twitter.com/1/statuses/home_timeline.json"); +}