1 /// Implementations of OAuth 1.0 server and client. You probably don't need this anymore; I haven't used it for years. 2 module arsd.oauth; 3 4 import arsd.curl; 5 import arsd.cgi; // for decodeVariables 6 import std.array; 7 static import std.uri; 8 static import std.algorithm; 9 import std.conv; 10 import std.string; 11 import std.random; 12 import std.base64; 13 import std.exception; 14 import std.datetime; 15 16 17 static if(__VERSION__ <= 2076) { 18 // compatibility shims with gdc 19 enum JSONType { 20 object = JSON_TYPE.OBJECT, 21 null_ = JSON_TYPE.NULL, 22 false_ = JSON_TYPE.FALSE, 23 true_ = JSON_TYPE.TRUE, 24 integer = JSON_TYPE.INTEGER, 25 float_ = JSON_TYPE.FLOAT, 26 array = JSON_TYPE.ARRAY, 27 string = JSON_TYPE.STRING, 28 uinteger = JSON_TYPE.UINTEGER 29 } 30 } 31 32 33 /////////////////////////////////////// 34 35 class FacebookApiException : Exception { 36 public this(string response, string token = null, string scopeRequired = null) { 37 this.token = token; 38 this.scopeRequired = scopeRequired; 39 super(response ~ "\nToken: " ~ token ~ "\nScope: " ~ scopeRequired); 40 } 41 42 string token; 43 string scopeRequired; 44 } 45 46 47 import arsd.curl; 48 import arsd.sha; 49 50 import std.digest.md; 51 52 import std.file; 53 54 55 // note when is a d_time, so unix_timestamp * 1000 56 Variant[string] postToFacebookWall(string[] info, string id, string message, string picture = null, string link = null, long when = 0, string linkDescription = null) { 57 string url = "https://graph.facebook.com/" ~ id ~ "/feed"; 58 59 string data = "access_token=" ~ std.uri.encodeComponent(info[1]); 60 data ~= "&message=" ~ std.uri.encodeComponent(message); 61 62 if(picture !is null && picture.length) 63 data ~= "&picture=" ~ std.uri.encodeComponent(picture); 64 if(link !is null && link.length) 65 data ~= "&link=" ~ std.uri.encodeComponent(link); 66 if(when) { 67 data ~= "&scheduled_publish_time=" ~ to!string(when / 1000); 68 data ~= "&published=false"; 69 } 70 if(linkDescription.length) 71 data ~= "&description=" ~ std.uri.encodeComponent(linkDescription); 72 73 auto response = curl(url, data); 74 75 auto res = jsonToVariant(response); 76 /+ 77 {"error":{"type":"OAuthException","message":"An active access token must be used to query information about the current user."}} 78 +/ 79 // assert(0, response); 80 81 auto var = res.get!(Variant[string]); 82 83 84 if("error" in var) { 85 auto error = var["error"].get!(Variant[string]); 86 87 throw new FacebookApiException(error["message"].get!string, info[1], 88 "scope" in error ? error["scope"].get!string : "publish_stream"); 89 } 90 91 return var; 92 } 93 94 version(with_arsd_jsvar) { 95 import arsd.jsvar; 96 var fbGraph(string token, string id, bool useCache = false, long maxCacheHours = 2) { 97 auto response = fbGraphImpl(token, id, useCache, maxCacheHours); 98 99 var ret = var.emptyObject; 100 101 if(response == "false") { 102 var v1 = id[1..$]; 103 ret["id"] = v1; 104 ret["name"] = v1 = "Private"; 105 ret["description"] = v1 = "This is a private facebook page. Please make it public in Facebook if you want to promote it."; 106 ret["link"] = v1 = "http://facebook.com?profile.php?id=" ~ id[1..$]; 107 ret["is_false"] = true; 108 return ret; 109 } 110 111 ret = var.fromJson(response); 112 113 if("error" in ret) { 114 auto error = ret.error; 115 116 if("message" in error) 117 throw new FacebookApiException(error["message"].get!string, token.length > 1 ? token : null, 118 "scope" in error ? error["scope"].get!string : null); 119 else 120 throw new FacebookApiException("couldn't get FB info"); 121 } 122 123 return ret; 124 } 125 } 126 127 Variant[string] fbGraph(string[] info, string id, bool useCache = false, long maxCacheHours = 2) { 128 auto response = fbGraphImpl(info[1], id, useCache, maxCacheHours); 129 130 if(response == "false") { 131 //throw new Exception("This page is private. Please make it public in Facebook."); 132 // we'll make dummy data so this still returns 133 134 Variant[string] ret; 135 136 Variant v1 = id[1..$]; 137 ret["id"] = v1; 138 ret["name"] = v1 = "Private"; 139 ret["description"] = v1 = "This is a private facebook page. Please make it public in Facebook if you want to promote it."; 140 ret["link"] = v1 = "http://facebook.com?profile.php?id=" ~ id[1..$]; 141 ret["is_false"] = true; 142 return ret; 143 } 144 145 auto res = jsonToVariant(response); 146 /+ 147 {"error":{"type":"OAuthException","message":"An active access token must be used to query information about the current user."}} 148 +/ 149 // assert(0, response); 150 151 auto var = res.get!(Variant[string]); 152 153 if("error" in var) { 154 auto error = var["error"].get!(Variant[string]); 155 156 if("message" in error) 157 throw new FacebookApiException(error["message"].get!string, info.length > 1 ? info[1] : null, 158 "scope" in error ? error["scope"].get!string : null); 159 else 160 throw new FacebookApiException("couldn't get FB info"); 161 } 162 163 return var; 164 165 } 166 167 // note ids=a,b,c works too. it returns an associative array of the ids requested. 168 string fbGraphImpl(string info, string id, bool useCache = false, long maxCacheHours = 2) { 169 string response; 170 171 string cacheFile; 172 173 char c = '?'; 174 175 if(id.indexOf("?") != -1) 176 c = '&'; 177 178 string url; 179 180 if(id[0] != '/') 181 id = "/" ~ id; 182 183 if(info !is null) 184 url = "https://graph.facebook.com" ~ id 185 ~ c ~ "access_token=" ~ info ~ "&format=json"; 186 else 187 url = "http://graph.facebook.com" ~ id 188 ~ c ~ "format=json"; 189 190 // this makes pagination easier. the initial / is there because it is added above 191 if(id.indexOf("/http://") == 0 || id.indexOf("/https://") == 0) 192 url = id[1 ..$]; 193 194 if(useCache) 195 cacheFile = "/tmp/fbGraphCache-" ~ hashToString(SHA1(url)); 196 197 if(useCache) { 198 if(std.file.exists(cacheFile)) { 199 if((Clock.currTime() - std.file.timeLastModified(cacheFile)) < dur!"hours"(maxCacheHours)) { 200 response = std.file.readText(cacheFile); 201 goto haveResponse; 202 } 203 } 204 } 205 206 try { 207 response = curl(url); 208 } catch(CurlException e) { 209 throw new FacebookApiException(e.msg); 210 } 211 212 if(useCache) { 213 std.file.write(cacheFile, response); 214 } 215 216 haveResponse: 217 assert(response.length); 218 219 return response; 220 } 221 222 223 224 string[string][] getBasicDataFromVariant(Variant[string] v) { 225 auto items = v["data"].get!(Variant[]); 226 return getBasicDataFromVariant(items); 227 } 228 229 string[string][] getBasicDataFromVariant(Variant[] items) { 230 string[string][] ret; 231 232 foreach(item; items) { 233 auto i = item.get!(Variant[string]); 234 235 string[string] l; 236 237 foreach(k, v; i) { 238 l[k] = to!string(v); 239 } 240 241 ret ~= l; 242 } 243 244 return ret; 245 } 246 247 248 ///////////////////////////////////// 249 250 251 252 253 254 255 256 257 258 259 260 /* ******************************* */ 261 262 /* OAUTH 1.0 */ 263 264 /* ******************************* */ 265 266 267 struct OAuthParams { 268 string apiKey; 269 string apiSecret; 270 string baseUrl; 271 string requestTokenPath; 272 string accessTokenPath; 273 string authorizePath; 274 } 275 276 OAuthParams twitter(string apiKey, string apiSecret) { 277 OAuthParams params; 278 279 params.apiKey = apiKey; 280 params.apiSecret = apiSecret; 281 282 params.baseUrl = "https://api.twitter.com"; 283 //params.baseUrl = "http://twitter.com"; 284 params.requestTokenPath = "/oauth/request_token"; 285 params.authorizePath = "/oauth/authorize"; 286 params.accessTokenPath = "/oauth/access_token"; 287 288 return params; 289 } 290 291 OAuthParams tumblr(string apiKey, string apiSecret) { 292 OAuthParams params; 293 294 params.apiKey = apiKey; 295 params.apiSecret = apiSecret; 296 297 params.baseUrl = "http://www.tumblr.com"; 298 params.requestTokenPath = "/oauth/request_token"; 299 params.authorizePath = "/oauth/authorize"; 300 params.accessTokenPath = "/oauth/access_token"; 301 302 return params; 303 } 304 305 OAuthParams linkedIn(string apiKey, string apiSecret) { 306 OAuthParams params; 307 308 params.apiKey = apiKey; 309 params.apiSecret = apiSecret; 310 311 params.baseUrl = "https://api.linkedin.com"; 312 params.requestTokenPath = "/uas/oauth/requestToken"; 313 params.accessTokenPath = "/uas/oauth/accessToken"; 314 params.authorizePath = "/uas/oauth/authorize"; 315 316 return params; 317 } 318 319 OAuthParams aWeber(string apiKey, string apiSecret) { 320 OAuthParams params; 321 322 params.apiKey = apiKey; 323 params.apiSecret = apiSecret; 324 325 params.baseUrl = "https://auth.aweber.com"; 326 params.requestTokenPath = "/1.1/oauth/request_token"; 327 params.accessTokenPath = "/1.1/oauth/access_token"; 328 params.authorizePath = "/1.1/oauth/authorize"; 329 330 // API Base: https://api.aweber.com/1.0/ 331 332 return params; 333 } 334 335 336 string tweet(OAuthParams params, string oauthToken, string tokenSecret, string message) { 337 assert(oauthToken.length); 338 assert(tokenSecret.length); 339 340 auto args = [ 341 "oauth_token" : oauthToken, 342 "token_secret" : tokenSecret, 343 ]; 344 345 auto data = "status=" ~ rawurlencode(message);//.replace("%3F", "?");//encodeVariables(["status" : message]); 346 347 auto ret = curlOAuth(params, "https://api.twitter.com" ~ "/1.1/statuses/update.json", args, "POST", data); 348 349 auto val = jsonToVariant(ret).get!(Variant[string]); 350 if("id_str" !in val) 351 throw new Exception("bad result from twitter: " ~ ret); 352 return val["id_str"].get!string; 353 } 354 355 import std.file; 356 /** 357 Redirects the user to the authorize page on the provider's website. 358 */ 359 void authorizeStepOne(Cgi cgi, OAuthParams params, string oauthCallback = null, string additionalOptions = null, string[string] additionalTokenArgs = null) { 360 if(oauthCallback is null) { 361 oauthCallback = cgi.getCurrentCompleteUri(); 362 if(oauthCallback.indexOf("?") == -1) 363 oauthCallback ~= "?oauth_step=two"; 364 else 365 oauthCallback ~= "&oauth_step=two"; 366 } 367 368 string[string] args; 369 if(oauthCallback.length) 370 args["oauth_callback"] = oauthCallback; 371 372 //foreach(k, v; additionalTokenArgs) 373 //args[k] = v; 374 375 auto moreArgs = encodeVariables(additionalTokenArgs); 376 if(moreArgs.length) 377 moreArgs = "?" ~ moreArgs; 378 auto ret = curlOAuth(params, params.baseUrl ~ params.requestTokenPath ~ moreArgs, 379 args, "POST", "", ""); 380 auto vals = decodeVariables(ret); 381 382 if("oauth_problem" in vals) 383 throw new Exception("OAuth problem: " ~ vals["oauth_problem"][0]); 384 385 if(vals.keys.length < 2) 386 throw new Exception(ret); 387 388 ///vals["fuck_you"] = [params.baseUrl ~ params.requestTokenPath]; 389 390 auto oauth_token = vals["oauth_token"][0]; 391 auto oauth_secret = vals["oauth_token_secret"][0]; 392 393 // need to save the secret for later 394 std.file.write("/tmp/oauth-token-secret-" ~ oauth_token, 395 oauth_secret); 396 397 // FIXME: make sure this doesn't break twitter etc 398 if("login_url" in vals) // apparently etsy does it this way... 399 cgi.setResponseLocation(vals["login_url"][0]); 400 else 401 cgi.setResponseLocation(params.baseUrl ~ params.authorizePath ~ "?" ~(additionalOptions.length ? (additionalOptions ~ "&") : "")~ "oauth_token=" ~ oauth_token); 402 } 403 404 /** 405 Gets the final token, given the stuff from step one. This should be called 406 from the callback in step one. 407 408 Returns [token, secret, raw original data (for extended processing - twitter also sends the screen_name and user_id there)] 409 */ 410 string[] authorizeStepTwo(const(Cgi) cgi, OAuthParams params) { 411 if("oauth_problem" in cgi.get) 412 throw new Exception("OAuth problem: " ~ cgi.get["oauth_problem"]); 413 414 string token = cgi.get["oauth_token"]; 415 string verifier = cgi.get["oauth_verifier"]; 416 417 // reload from file written above. FIXME: clean up old shit too 418 string secret = std.file.readText("/tmp/oauth-token-secret-" ~ token); 419 // don't need it anymore... 420 std.file.remove("/tmp/oauth-token-secret-" ~ token); 421 422 423 auto ret = curlOAuth(params, params.baseUrl ~ params.accessTokenPath, 424 ["oauth_token" : token, 425 "oauth_verifier" : verifier, 426 "token_secret" : secret], "POST", "", ""); 427 428 auto vars = decodeVariables(ret); 429 430 return [vars["oauth_token"][0], vars["oauth_token_secret"][0], ret]; 431 } 432 433 434 435 /** 436 Note in oauthValues: 437 It creates the nonce, signature_method, version, consumer_key, and timestamp 438 ones inside this function - you don't have to do it. 439 440 Just put in the values specific to your call. 441 442 oauthValues["token_secret"] if present, is concated into the signing string. Don't 443 put it in for the early steps! 444 */ 445 446 import core.stdc.stdlib; 447 448 string curlOAuth(OAuthParams auth, string url, string[string] oauthValues, string method = null,string data = null, string contentType = "application/x-www-form-urlencoded") { 449 450 //string oauth_callback; // from user 451 452 oauthValues["oauth_consumer_key"] = auth.apiKey; 453 oauthValues["oauth_nonce"] = makeNonce(); 454 oauthValues["oauth_signature_method"] = "HMAC-SHA1"; 455 456 oauthValues["oauth_timestamp"] = to!string(Clock.currTime().toUTC().toUnixTime()); 457 oauthValues["oauth_version"] = "1.0"; 458 459 auto questionMark = std..string.indexOf(url, "?"); 460 461 string signWith = std.uri.encodeComponent(auth.apiSecret) ~ "&"; 462 if("token_secret" in oauthValues) { 463 signWith ~= std.uri.encodeComponent(oauthValues["token_secret"]); 464 oauthValues.remove("token_secret"); 465 } 466 467 if(method is null) 468 method = data is null ? "GET" : "POST"; 469 470 auto baseString = getSignatureBaseString( 471 method, 472 questionMark == -1 ? url : url[0..questionMark], 473 questionMark == -1 ? "" : url[questionMark+1 .. $], 474 oauthValues, 475 contentType == "application/x-www-form-urlencoded" ? data : null 476 ); 477 478 string oauth_signature = /*std.uri.encodeComponent*/(cast(string) 479 Base64.encode(mhashSign(baseString, signWith, MHASH_SHA1))); 480 481 oauthValues["oauth_signature"] = oauth_signature; 482 483 string oauthHeader; 484 bool outputted = false; 485 Pair[] pairs; 486 foreach(k, v; oauthValues) { 487 pairs ~= Pair(k, v); 488 } 489 490 foreach(pair; std.algorithm.sort(pairs)) { 491 if(outputted) 492 oauthHeader ~= ", "; 493 else 494 outputted = true; 495 496 oauthHeader ~= pair.output(true); 497 } 498 499 return curlAuth(url, data, null, null, contentType, method, ["Authorization: OAuth " ~ oauthHeader]); 500 } 501 502 bool isOAuthRequest(Cgi cgi) { 503 if(cgi.authorization.length < 5 || cgi.authorization[0..5] != "OAuth") 504 return false; 505 return true; 506 } 507 508 string getApiKeyFromRequest(Cgi cgi) { 509 enforce(isOAuthRequest(cgi)); 510 auto variables = split(cgi.authorization[6..$], ","); 511 512 foreach(var; variables) 513 if(var.startsWith("oauth_consumer_key")) 514 return var["oauth_consumer_key".length + 3 .. $ - 1]; // trimming quotes too 515 throw new Exception("api key not present"); 516 } 517 518 string getTokenFromRequest(Cgi cgi) { 519 enforce(isOAuthRequest(cgi)); 520 auto variables = split(cgi.authorization[6..$], ","); 521 522 foreach(var; variables) 523 if(var.startsWith("oauth_token")) 524 return var["oauth_token".length + 3 .. $ - 1]; // trimming quotes too 525 return null; 526 } 527 528 // FIXME check timestamp and maybe nonce too 529 530 bool isSignatureValid(Cgi cgi, string apiSecret, string tokenSecret) { 531 enforce(isOAuthRequest(cgi)); 532 auto variables = split(cgi.authorization[6..$], ","); 533 534 string[string] oauthValues; 535 foreach(var; variables) { 536 auto it = var.split("="); 537 oauthValues[it[0]] = it[1][1 .. $ - 1]; // trimming quotes 538 } 539 540 auto url = cgi.getCurrentCompleteUri(); 541 542 auto questionMark = std..string.indexOf(url, "?"); 543 544 string signWith = std.uri.encodeComponent(apiSecret) ~ "&"; 545 if(tokenSecret.length) 546 signWith ~= std.uri.encodeComponent(tokenSecret); 547 548 auto method = to!string(cgi.requestMethod); 549 550 if("oauth_signature" !in oauthValues) 551 return false; 552 553 auto providedSignature = oauthValues["oauth_signature"]; 554 555 oauthValues.remove("oauth_signature"); 556 557 string oauth_signature = std.uri.encodeComponent(cast(string) 558 Base64.encode(mhashSign( 559 getSignatureBaseString( 560 method, 561 questionMark == -1 ? url : url[0..questionMark], 562 questionMark == -1 ? "" : url[questionMark+1 .. $], 563 oauthValues, 564 cgi.postArray // FIXME: if this was a file upload, this isn't actually right 565 ), signWith, MHASH_SHA1))); 566 567 return oauth_signature == providedSignature; 568 569 } 570 571 string makeNonce() { 572 auto val = to!string(uniform(uint.min, uint.max)) ~ to!string(Clock.currTime().stdTime); 573 574 return val; 575 } 576 577 struct Pair { 578 string name; 579 string value; 580 581 string output(bool useQuotes = false) { 582 if(useQuotes) 583 return std.uri.encodeComponent(name) ~ "=\"" ~ rawurlencode(value) ~ "\""; 584 else 585 return std.uri.encodeComponent(name) ~ "=" ~ rawurlencode(value); 586 } 587 588 int opCmp(Pair rhs) { 589 // FIXME: is name supposed to be encoded? 590 int val = std..string.cmp(name, rhs.name); 591 592 if(val == 0) 593 val = std..string.cmp(value, rhs.value); 594 595 return val; 596 } 597 } 598 string getSignatureBaseString( 599 string method, 600 string protocolHostAndPath, 601 string queryStringContents, 602 string[string] authorizationHeaderContents, 603 in string[][string] postArray) 604 { 605 string baseString; 606 607 baseString ~= method; 608 baseString ~= "&"; 609 baseString ~= std.uri.encodeComponent(protocolHostAndPath); 610 baseString ~= "&"; 611 612 auto getArray = decodeVariables(queryStringContents); 613 614 Pair[] pairs; 615 616 foreach(k, vals; getArray) 617 foreach(v; vals) 618 pairs ~= Pair(k, v); 619 foreach(k, vals; postArray) 620 foreach(v; vals) 621 pairs ~= Pair(k, v); 622 foreach(k, v; authorizationHeaderContents) 623 pairs ~= Pair(k, v); 624 625 bool outputted = false; 626 627 string params; 628 foreach(pair; std.algorithm.sort(pairs)) { 629 if(outputted) 630 params ~= "&"; 631 else 632 outputted = true; 633 params ~= pair.output(); 634 } 635 636 baseString ~= std.uri.encodeComponent(params); 637 638 return baseString; 639 } 640 641 642 string getSignatureBaseString( 643 string method, 644 string protocolHostAndPath, 645 string queryStringContents, 646 string[string] authorizationHeaderContents, 647 string postBodyIfWwwEncoded) 648 { 649 return getSignatureBaseString( 650 method, 651 protocolHostAndPath, 652 queryStringContents, 653 authorizationHeaderContents, 654 decodeVariables(postBodyIfWwwEncoded)); 655 } 656 657 /***************************************/ 658 659 // OAuth 2.0 as used by Facebook // 660 661 /***************************************/ 662 663 immutable(ubyte)[] base64UrlDecode(string e) { 664 string encoded = e.idup; 665 while (encoded.length % 4) { 666 encoded ~= "="; // add padding 667 } 668 669 // convert base64 URL to standard base 64 670 encoded = encoded.replace("-", "+"); 671 encoded = encoded.replace("_", "/"); 672 673 auto ugh = Base64.decode(encoded); 674 return assumeUnique(ugh); 675 } 676 677 Ret parseSignedRequest(Ret = Variant)(in string req, string apisecret) { 678 auto parts = req.split("."); 679 680 immutable signature = parts[0]; 681 immutable jsonEncoded = parts[1]; 682 683 auto expected = mhashSign(jsonEncoded, apisecret, MHASH_SHA256); 684 auto got = base64UrlDecode(signature); 685 686 enforce(expected == got, "Signatures didn't match"); 687 688 auto json = cast(string) base64UrlDecode(jsonEncoded); 689 690 static if(is(Ret == Variant)) 691 return jsonToVariant(json); 692 else 693 return Ret.fromJson(json); 694 } 695 696 string stripWhitespace(string w) { 697 return w.replace("\t", "").replace("\n", "").replace(" ", ""); 698 } 699 700 string translateCodeToAccessToken(string code, string redirectUrl, string appId, string apiSecret) { 701 string res = curl(stripWhitespace("https://graph.facebook.com/oauth/access_token? 702 client_id="~appId~"&redirect_uri="~std.uri.encodeComponent(redirectUrl)~"& 703 client_secret="~apiSecret~"&code=" ~ std.uri.encodeComponent(code) 704 )); 705 706 if(res.indexOf("access_token=") == -1) { 707 throw new Exception("Couldn't translate code to access token. [" ~ res ~ "]"); 708 } 709 710 auto vars = decodeVariablesSingle(res); 711 return vars["access_token"]; 712 } 713 714 /+ 715 716 void updateFbGraphPermissions(string token) { 717 fbGraph([null, token], "/me/permissions", true, -1); // use the cache, but only read if it is in the future - basically, force a cache refresh 718 fbGraph([null, token], "/me/friends", true, -1); // do the same thing for friends.. 719 } 720 721 auto fbGraphPermissions(string token) { 722 return fbGraph([null, token], "/me/permissions", true, 36); // use the cache 723 } 724 725 enum FacebookPermissions { 726 user_likes, 727 friends_likes, 728 publish_stream, 729 publish_actions, 730 offline_access, 731 manage_pages, 732 } 733 734 bool hasPermission(DataObject person, FacebookPermissions permission) { 735 version(live) {} else return true; // on dev, just skip this stuff 736 737 if(person.facebook_access_token.length == 0) 738 return false; 739 try { 740 auto perms = getBasicDataFromVariant(fbGraphPermissions(person. facebook_access_token))[0]; 741 return (to!string(permission) in perms) ? true : false; 742 } catch(FacebookApiException e) { 743 return false; // the token doesn't work 744 } 745 746 return false; 747 } 748 749 +/ 750 751 752 /****************************************/ 753 754 // Generic helper functions for web work 755 756 /****************************************/ 757 758 import std.variant; 759 import std.json; 760 761 Variant jsonToVariant(string json) { 762 auto decoded = parseJSON(json); 763 return jsonValueToVariant(decoded); 764 } 765 766 Variant jsonValueToVariant(JSONValue v) { 767 Variant ret; 768 769 final switch(v.type) { 770 case JSONType..string: 771 ret = v.str; 772 break; 773 case JSONType.uinteger: 774 ret = v.uinteger; 775 break; 776 case JSONType.integer: 777 ret = v.integer; 778 break; 779 case JSONType.float_: 780 ret = v.floating; 781 break; 782 case JSONType.object: 783 Variant[string] obj; 784 foreach(k, val; v.object) { 785 obj[k] = jsonValueToVariant(val); 786 } 787 788 ret = obj; 789 break; 790 case JSONType.array: 791 Variant[] arr; 792 foreach(i; v.array) { 793 arr ~= jsonValueToVariant(i); 794 } 795 796 ret = arr; 797 break; 798 case JSONType.true_: 799 ret = true; 800 break; 801 case JSONType.false_: 802 ret = false; 803 break; 804 case JSONType.null_: 805 ret = null; 806 break; 807 } 808 809 return ret; 810 } 811 812 /***************************************/ 813 814 // Interface to C lib for signing 815 816 /***************************************/ 817 818 extern(C) { 819 alias int hashid; 820 MHASH mhash_hmac_init(hashid, const scope void*, int, int); 821 bool mhash(const scope void*, const scope void*, int); 822 int mhash_get_hash_pblock(hashid); 823 byte* mhash_hmac_end(MHASH); 824 int mhash_get_block_size(hashid); 825 826 hashid MHASH_MD5 = 1; 827 hashid MHASH_SHA1 = 2; 828 hashid MHASH_SHA256 = 17; 829 alias void* MHASH; 830 } 831 832 ubyte[] mhashSign(string data, string signWith, hashid algorithm) @trusted { 833 auto td = mhash_hmac_init(algorithm, signWith.ptr, cast(int) signWith.length, 834 mhash_get_hash_pblock(algorithm)); 835 836 mhash(td, data.ptr, cast(int) data.length); 837 auto mac = mhash_hmac_end(td); 838 ubyte[] ret; 839 840 for (int j = 0; j < mhash_get_block_size(algorithm); j++) { 841 ret ~= cast(ubyte) mac[j]; 842 } 843 844 /* 845 string ret; 846 847 for (int j = 0; j < mhash_get_block_size(algorithm); j++) { 848 ret ~= std.string.format("%.2x", mac[j]); 849 } 850 */ 851 852 return ret; 853 } 854 855 pragma(lib, "mhash");