github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/pvl/parse_test.go (about) 1 // Copyright 2016 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package pvl 5 6 import ( 7 "testing" 8 9 "github.com/keybase/client/go/protocol/keybase1" 10 ) 11 12 // TestParse parses the hardcoded string 13 func TestParse(t *testing.T) { 14 p, err := parse(testPvlString) 15 if err != nil { 16 t.Fatalf("parse failed: %v", err) 17 } 18 if p.PvlVersion < 0 { 19 t.Fatalf("version should be >=0: %v", p.PvlVersion) 20 } 21 } 22 23 // TestParse2 checks a few of the parse output's details. 24 func TestParse2(t *testing.T) { 25 p, err := parse(testPvlString) 26 if err != nil { 27 t.Fatalf("parse failed: %v", err) 28 } 29 if p.PvlVersion != 1 { 30 t.Fatalf("version should be 1 got %v", p.PvlVersion) 31 } 32 if p.Revision != 1 { 33 t.Fatalf("revision should be 1") 34 } 35 cbss, ok := p.Services.Map[keybase1.ProofType_TWITTER] 36 if !ok { 37 t.Fatalf("no twittter service entry") 38 } 39 if len(cbss) < 1 { 40 t.Fatalf("no scripts") 41 } 42 cbs := cbss[0] 43 if len(cbs.Instructions) < 1 { 44 t.Fatalf("empty script") 45 } 46 if cbs.Instructions[0].RegexCapture == nil { 47 t.Fatalf("first instruction is not a regex capture") 48 } 49 } 50 51 var testPvlString = ` 52 { 53 "pvl_version": 1, 54 "revision": 1, 55 "services": { 56 "coinbase": [ 57 [ 58 { 59 "fill": { 60 "into": "our_url", 61 "with": "https://coinbase.com/%{username_service}/public-key" 62 } 63 }, 64 { 65 "fetch": { 66 "from": "our_url", 67 "kind": "html" 68 } 69 }, 70 { 71 "selector_css": { 72 "error": [ 73 "FAILED_PARSE", 74 "Couldn't find a div $(pre.statement)" 75 ], 76 "into": "haystack", 77 "selectors": [ 78 "pre.statement", 79 0 80 ] 81 } 82 }, 83 { 84 "assert_find_base64": { 85 "haystack": "haystack", 86 "needle": "sig" 87 }, 88 "error": [ 89 "TEXT_NOT_FOUND", 90 "signature not found in body" 91 ] 92 } 93 ] 94 ], 95 "dns": [ 96 [ 97 { 98 "assert_regex_match": { 99 "error": [ 100 "NOT_FOUND", 101 "matching DNS entry not found" 102 ], 103 "from": "txt", 104 "pattern": "^keybase-site-verification=%{sig_id_medium}$" 105 } 106 } 107 ] 108 ], 109 "facebook": [ 110 [ 111 { 112 "assert_regex_match": { 113 "error": [ 114 "BAD_USERNAME", 115 "Invalid characters in username '%{username_service}'" 116 ], 117 "from": "username_service", 118 "pattern": "^[a-zA-Z0-9\\.]+$" 119 } 120 }, 121 { 122 "regex_capture": { 123 "error": [ 124 "BAD_API_URL", 125 "Bad hint from server; URL should start with 'https://m.facebook.com/%{username_service}/posts/', got '%{hint_url}'" 126 ], 127 "from": "hint_url", 128 "into": [ 129 "unused1", 130 "username_from_url", 131 "post_id" 132 ], 133 "pattern": "^https://(m|www)\\.facebook\\.com/([^/]*)/posts/([0-9]+)$" 134 } 135 }, 136 { 137 "assert_compare": { 138 "a": "username_from_url", 139 "b": "username_service", 140 "cmp": "stripdots-then-cicmp", 141 "error": [ 142 "BAD_API_URL", 143 "Bad hint from server; username in URL should match '%{username_service}', received '%{username_from_url}'" 144 ] 145 } 146 }, 147 { 148 "fill": { 149 "into": "our_url", 150 "with": "https://www.facebook.com/%{username_from_url}/posts/%{post_id}" 151 } 152 }, 153 { 154 "fetch": { 155 "from": "our_url", 156 "kind": "html" 157 } 158 }, 159 { 160 "selector_css": { 161 "data": true, 162 "error": [ 163 "FAILED_PARSE", 164 "Could not find proof markup comment in Facebook's response" 165 ], 166 "into": "first_code_comment", 167 "selectors": [ 168 "code", 169 0, 170 { 171 "contents": true 172 }, 173 0 174 ] 175 } 176 }, 177 { 178 "replace_all": { 179 "from": "first_code_comment", 180 "into": "fcc2", 181 "new": "--", 182 "old": "-\\-\\" 183 } 184 }, 185 { 186 "replace_all": { 187 "from": "fcc2", 188 "into": "fcc3", 189 "new": "\\", 190 "old": "\\\\" 191 } 192 }, 193 { 194 "parse_html": { 195 "error": [ 196 "FAILED_PARSE", 197 "Failed to parse proof markup comment in Facebook post: %{fcc3}" 198 ], 199 "from": "fcc3" 200 } 201 }, 202 { 203 "selector_css": { 204 "error": [ 205 "FAILED_PARSE", 206 "Could not find link text in Facebook's response" 207 ], 208 "into": "link_text", 209 "selectors": [ 210 "div.userContent+div a", 211 1 212 ] 213 } 214 }, 215 { 216 "whitespace_normalize": { 217 "from": "link_text", 218 "into": "link_text_nw" 219 } 220 }, 221 { 222 "regex_capture": { 223 "error": [ 224 "TEXT_NOT_FOUND", 225 "Could not find Verifying myself: I am %{username_keybase} on Keybase.io. (%{sig_id_medium})" 226 ], 227 "from": "link_text_nw", 228 "into": [ 229 "username_from_link", 230 "sig_from_link" 231 ], 232 "pattern": "^Verifying myself: I am (\\S+) on Keybase.io. (\\S+)$" 233 } 234 }, 235 { 236 "assert_compare": { 237 "a": "username_from_link", 238 "b": "username_keybase", 239 "cmp": "cicmp", 240 "error": [ 241 "BAD_USERNAME", 242 "Wrong Keybase username in post '%{username_from_link}' should be '%{username_keybase}'" 243 ] 244 } 245 }, 246 { 247 "assert_compare": { 248 "a": "sig_id_medium", 249 "b": "sig_from_link", 250 "cmp": "exact", 251 "error": [ 252 "BAD_SIGNATURE", 253 "Could not find sig; '%{sig_from_link}' != '%{sig_id_medium}'" 254 ] 255 } 256 } 257 ] 258 ], 259 "generic_web_site": [ 260 [ 261 { 262 "assert_regex_match": { 263 "error": [ 264 "BAD_API_URL", 265 "Bad hint from server; didn't recognize API url: \"%{hint_url}\"" 266 ], 267 "from": "hint_url", 268 "pattern": "^%{protocol}://%{hostname}/(?:\\.well-known/keybase\\.txt|keybase\\.txt)$" 269 } 270 }, 271 { 272 "fetch": { 273 "from": "hint_url", 274 "into": "blob", 275 "kind": "string" 276 } 277 }, 278 { 279 "assert_find_base64": { 280 "error": [ 281 "TEXT_NOT_FOUND", 282 "signature not found in body" 283 ], 284 "haystack": "blob", 285 "needle": "sig" 286 } 287 } 288 ] 289 ], 290 "github": [ 291 [ 292 { 293 "regex_capture": { 294 "error": [ 295 "BAD_API_URL", 296 "Bad hint from server; URL should start with either https://gist.github.com OR https://gist.githubusercontent.com" 297 ], 298 "from": "hint_url", 299 "into": [ 300 "username_from_url" 301 ], 302 "pattern": "^https://gist\\.github(?:usercontent)?\\.com/([^/]*)/.*$" 303 } 304 }, 305 { 306 "assert_compare": { 307 "a": "username_from_url", 308 "b": "username_service", 309 "cmp": "cicmp", 310 "error": [ 311 "BAD_API_URL", 312 "Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}" 313 ] 314 } 315 }, 316 { 317 "fetch": { 318 "from": "hint_url", 319 "into": "haystack", 320 "kind": "string" 321 } 322 }, 323 { 324 "assert_find_base64": { 325 "haystack": "haystack", 326 "needle": "sig" 327 }, 328 "error": [ 329 "TEXT_NOT_FOUND", 330 "signature not found in body" 331 ] 332 } 333 ] 334 ], 335 "hackernews": [ 336 [ 337 { 338 "regex_capture": { 339 "error": [ 340 "BAD_API_URL", 341 "Bad hint from server; URL should match https://hacker-news.firebaseio.com/v0/user/%{username_service}/about.json" 342 ], 343 "from": "hint_url", 344 "into": [ 345 "username_from_url" 346 ], 347 "pattern": "^https://hacker-news\\.firebaseio\\.com/v0/user/([^/]+)/about.json$" 348 } 349 }, 350 { 351 "assert_compare": { 352 "a": "username_from_url", 353 "b": "username_service", 354 "cmp": "cicmp", 355 "error": [ 356 "BAD_API_URL", 357 "Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}" 358 ] 359 } 360 }, 361 { 362 "fetch": { 363 "from": "hint_url", 364 "into": "profile", 365 "kind": "string" 366 } 367 }, 368 { 369 "assert_regex_match": { 370 "error": [ 371 "TEXT_NOT_FOUND", 372 "Posted text does not include signature '%{sig_id_medium}'" 373 ], 374 "from": "profile", 375 "pattern": "^.*%{sig_id_medium}.*$" 376 } 377 } 378 ] 379 ], 380 "reddit": [ 381 [ 382 { 383 "regex_capture": { 384 "error": [ 385 "BAD_API_URL", 386 "URL should start with 'https://www.reddit.com/r/keybaseproofs'" 387 ], 388 "from": "hint_url", 389 "into": [ 390 "subreddit_from_url", 391 "path_remainder" 392 ], 393 "pattern": "^https://www.reddit.com/r/([^/]+)/(.*)$" 394 } 395 }, 396 { 397 "assert_regex_match": { 398 "case_insensitive": true, 399 "error": [ 400 "BAD_API_URL", 401 "URL contained wrong subreddit '%{subreddit_from_url}' !+ 'keybaseproofs'" 402 ], 403 "from": "subreddit_from_url", 404 "pattern": "^keybaseproofs$" 405 } 406 }, 407 { 408 "fetch": { 409 "from": "hint_url", 410 "kind": "json" 411 } 412 }, 413 { 414 "selector_json": { 415 "error": [ 416 "CONTENT_MISSING", 417 "Could not find 'kind' in json" 418 ], 419 "into": "kind", 420 "selectors": [ 421 0, 422 "kind" 423 ] 424 } 425 }, 426 { 427 "assert_regex_match": { 428 "error": [ 429 "CONTENT_FAILURE", 430 "Wanted a post of type 'Listing', but got '%{kind}'" 431 ], 432 "from": "kind", 433 "pattern": "^Listing$" 434 } 435 }, 436 { 437 "selector_json": { 438 "error": [ 439 "CONTENT_MISSING", 440 "Could not find inner 'kind' in json" 441 ], 442 "into": "inner_kind", 443 "selectors": [ 444 0, 445 "data", 446 "children", 447 0, 448 "kind" 449 ] 450 } 451 }, 452 { 453 "assert_regex_match": { 454 "error": [ 455 "CONTENT_FAILURE", 456 "Wanted a child of type 't3' but got '%{inner_kind}'" 457 ], 458 "from": "inner_kind", 459 "pattern": "^t3$" 460 } 461 }, 462 { 463 "selector_json": { 464 "error": [ 465 "CONTENT_MISSING", 466 "Could not find 'subreddit' in json" 467 ], 468 "into": "subreddit_from_json", 469 "selectors": [ 470 0, 471 "data", 472 "children", 473 0, 474 "data", 475 "subreddit" 476 ] 477 } 478 }, 479 { 480 "assert_regex_match": { 481 "case_insensitive": true, 482 "error": [ 483 "CONTENT_FAILURE", 484 "Wrong subreddit %{subreddit_from_json}" 485 ], 486 "from": "subreddit_from_json", 487 "pattern": "^keybaseproofs$" 488 } 489 }, 490 { 491 "selector_json": { 492 "error": [ 493 "CONTENT_MISSING", 494 "Could not find author in json" 495 ], 496 "into": "author", 497 "selectors": [ 498 0, 499 "data", 500 "children", 501 0, 502 "data", 503 "author" 504 ] 505 } 506 }, 507 { 508 "assert_compare": { 509 "a": "author", 510 "b": "username_service", 511 "cmp": "cicmp", 512 "error": [ 513 "BAD_USERNAME", 514 "Bad post author; wanted '%{username_service}' but got '%{author}'" 515 ] 516 } 517 }, 518 { 519 "selector_json": { 520 "error": [ 521 "CONTENT_MISSING", 522 "Could not find title in json" 523 ], 524 "into": "title", 525 "selectors": [ 526 0, 527 "data", 528 "children", 529 0, 530 "data", 531 "title" 532 ] 533 } 534 }, 535 { 536 "assert_regex_match": { 537 "error": [ 538 "TITLE_NOT_FOUND", 539 "Missing signature ID (%{sig_id_medium})) in post title '%{title}'" 540 ], 541 "from": "title", 542 "pattern": "^.*%{sig_id_medium}.*$" 543 } 544 }, 545 { 546 "selector_json": { 547 "error": [ 548 "CONTENT_MISSING", 549 "Could not find selftext in json" 550 ], 551 "into": "selftext", 552 "selectors": [ 553 0, 554 "data", 555 "children", 556 0, 557 "data", 558 "selftext" 559 ] 560 } 561 }, 562 { 563 "assert_find_base64": { 564 "error": [ 565 "TEXT_NOT_FOUND", 566 "signature not found in body" 567 ], 568 "haystack": "selftext", 569 "needle": "sig" 570 } 571 } 572 ] 573 ], 574 "rooter": [ 575 [ 576 { 577 "assert_regex_match": { 578 "case_insensitive": true, 579 "from": "hint_url", 580 "pattern": "^https?://[\\w:_\\-\\.]+/_/api/1\\.0/rooter/%{username_service}/.*$" 581 } 582 }, 583 { 584 "fetch": { 585 "from": "hint_url", 586 "kind": "json" 587 } 588 }, 589 { 590 "selector_json": { 591 "into": "name", 592 "selectors": [ 593 "status", 594 "name" 595 ] 596 } 597 }, 598 { 599 "assert_regex_match": { 600 "case_insensitive": true, 601 "from": "name", 602 "pattern": "^ok$" 603 } 604 }, 605 { 606 "selector_json": { 607 "into": "post", 608 "selectors": [ 609 "toot", 610 "post" 611 ] 612 } 613 }, 614 { 615 "assert_regex_match": { 616 "from": "post", 617 "pattern": "^.*%{sig_id_medium}.*$" 618 } 619 } 620 ] 621 ], 622 "twitter": [ 623 [ 624 { 625 "regex_capture": { 626 "error": [ 627 "BAD_API_URL", 628 "Bad hint from server; URL should start with 'https://twitter.com/%{username_service}/'" 629 ], 630 "from": "hint_url", 631 "into": [ 632 "username_from_url" 633 ], 634 "pattern": "^https://twitter\\.com/([^/]+)/.*$" 635 } 636 }, 637 { 638 "assert_compare": { 639 "a": "username_from_url", 640 "b": "username_service", 641 "cmp": "cicmp", 642 "error": [ 643 "BAD_API_URL", 644 "Bad hint from server; URL should contain username matching %{username_service}; got %{username_from_url}" 645 ] 646 } 647 }, 648 { 649 "fetch": { 650 "from": "hint_url", 651 "kind": "html" 652 } 653 }, 654 { 655 "selector_css": { 656 "attr": "data-screen-name", 657 "error": [ 658 "FAILED_PARSE", 659 "Couldn't find a div $(div.permalink-tweet-container div.permalink-tweet).eq(0)" 660 ], 661 "into": "data_screen_name", 662 "selectors": [ 663 "div.permalink-tweet-container div.permalink-tweet", 664 0 665 ] 666 } 667 }, 668 { 669 "assert_compare": { 670 "a": "data_screen_name", 671 "b": "username_service", 672 "cmp": "cicmp", 673 "error": [ 674 "BAD_USERNAME", 675 "Bad post authored: wanted '%{username_service}' but got '%{data_screen_name}'" 676 ] 677 } 678 }, 679 { 680 "selector_css": { 681 "error": [ 682 "CONTENT_MISSING", 683 "Missing <div class='tweet-text'> container for tweet" 684 ], 685 "into": "tweet_contents", 686 "selectors": [ 687 "div.permalink-tweet-container div.permalink-tweet", 688 0, 689 "p.tweet-text", 690 0 691 ] 692 } 693 }, 694 { 695 "whitespace_normalize": { 696 "from": "tweet_contents", 697 "into": "tweet_contents_nw" 698 } 699 }, 700 { 701 "regex_capture": { 702 "error": [ 703 "DELETED", 704 "Could not find 'Verifying myself: I am %{username_keybase} on Keybase.io. %{sig_id_short}'" 705 ], 706 "from": "tweet_contents_nw", 707 "into": [ 708 "username_from_tweet_contents", 709 "sig_from_tweet_contents" 710 ], 711 "pattern": "^ *(?:@[a-zA-Z0-9_-]+\\s*)* *Verifying myself: I am ([A-Za-z0-9_]+) on Keybase\\.io\\. (\\S+) */.*$" 712 } 713 }, 714 { 715 "assert_compare": { 716 "a": "username_from_tweet_contents", 717 "b": "username_keybase", 718 "cmp": "cicmp", 719 "error": [ 720 "BAD_USERNAME", 721 "Wrong username in tweet '%{username_from_tweet_contents}' should be '%{username_keybase}'" 722 ] 723 } 724 }, 725 { 726 "assert_regex_match": { 727 "error": [ 728 "TEXT_NOT_FOUND", 729 "Could not find sig '%{sig_from_tweet_contents}' != '%{sig_id_short}'" 730 ], 731 "from": "sig_from_tweet_contents", 732 "pattern": "^%{sig_id_short}$" 733 } 734 } 735 ] 736 ] 737 } 738 } 739 `