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