1// Copyright (c) 2019, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15//go:build interactive 16// +build interactive 17 18package main 19 20import ( 21 "bytes" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 neturl "net/url" 27 "os" 28 "os/exec" 29 "os/signal" 30 "path/filepath" 31 "reflect" 32 "strconv" 33 "strings" 34 "syscall" 35 36 "boringssl.googlesource.com/boringssl/util/fipstools/acvp/acvptool/acvp" 37 "golang.org/x/crypto/ssh/terminal" 38) 39 40const interactiveModeSupported = true 41 42func updateTerminalSize(term *terminal.Terminal) { 43 width, height, err := terminal.GetSize(0) 44 if err != nil { 45 return 46 } 47 term.SetSize(width, height) 48} 49 50func skipWS(node *node32) *node32 { 51 for ; node != nil && node.pegRule == ruleWS; node = node.next { 52 } 53 return node 54} 55 56func assertNodeType(node *node32, rule pegRule) { 57 if node.pegRule != rule { 58 panic(fmt.Sprintf("expected %q, found %q", rul3s[rule], rul3s[node.pegRule])) 59 } 60} 61 62type Object interface { 63 String() (string, error) 64 Index(string) (Object, error) 65 Search(acvp.Query) (Object, error) 66 Action(action string, args []string) error 67} 68 69type ServerObjectSet struct { 70 env *Env 71 name string 72 searchKeys map[string][]acvp.Relation 73 resultType reflect.Type 74 subObjects map[string]func(*Env, string) (Object, error) 75 canEnumerate bool 76} 77 78func (set ServerObjectSet) String() (string, error) { 79 if !set.canEnumerate { 80 return "[object set " + set.name + "]", nil 81 } 82 83 data := reflect.New(reflect.SliceOf(set.resultType)).Interface() 84 if err := set.env.server.GetPaged(data, "acvp/v1/"+set.name, nil); err != nil { 85 return "", err 86 } 87 ret, err := json.MarshalIndent(data, "", " ") 88 return string(ret), err 89} 90 91func (set ServerObjectSet) Index(indexStr string) (Object, error) { 92 index, err := strconv.ParseUint(indexStr, 0, 64) 93 if err != nil { 94 return nil, fmt.Errorf("object set indexes must be unsigned integers, trying to parse %q failed: %s", indexStr, err) 95 } 96 return ServerObject{&set, index}, nil 97} 98 99func (set ServerObjectSet) Search(condition acvp.Query) (Object, error) { 100 if set.searchKeys == nil { 101 return nil, errors.New("this object set cannot be searched") 102 } 103 104 for _, conj := range condition { 105 NextCondition: 106 for _, cond := range conj { 107 allowed, ok := set.searchKeys[cond.Param] 108 if !ok { 109 return nil, fmt.Errorf("search key %q not valid for this object set", cond.Param) 110 } 111 112 for _, rel := range allowed { 113 if rel == cond.Relation { 114 continue NextCondition 115 } 116 } 117 118 return nil, fmt.Errorf("search key %q cannot be used with relation %q", cond.Param, cond.Relation.String()) 119 } 120 } 121 122 return Search{ServerObjectSet: set, query: condition}, nil 123} 124 125func (set ServerObjectSet) Action(action string, args []string) error { 126 switch action { 127 default: 128 return fmt.Errorf("unknown action %q", action) 129 130 case "new": 131 if len(args) != 0 { 132 return fmt.Errorf("found %d arguments but %q takes none", len(args), action) 133 } 134 135 newContents, err := edit("") 136 if err != nil { 137 return err 138 } 139 140 if strings.TrimSpace(string(newContents)) == "" { 141 io.WriteString(set.env.term, "Resulting file was empty. Ignoring.\n") 142 return nil 143 } 144 145 var result map[string]interface{} 146 if err := set.env.server.Post(&result, "acvp/v1/"+set.name, newContents); err != nil { 147 return err 148 } 149 150 // In case it's a testSession that was just created, poke any access token 151 // into the server's lookup table and the cache. 152 if urlInterface, ok := result["url"]; ok { 153 if url, ok := urlInterface.(string); ok { 154 if tokenInterface, ok := result["accessToken"]; ok { 155 if token, ok := tokenInterface.(string); ok { 156 for strings.HasPrefix(url, "/") { 157 url = url[1:] 158 } 159 set.env.server.PrefixTokens[url] = token 160 if len(set.env.config.SessionTokensCache) > 0 { 161 os.WriteFile(filepath.Join(set.env.config.SessionTokensCache, neturl.PathEscape(url))+".token", []byte(token), 0600) 162 } 163 } 164 } 165 } 166 } 167 168 ret, err := json.MarshalIndent(result, "", " ") 169 if err != nil { 170 return err 171 } 172 set.env.term.Write(ret) 173 return nil 174 } 175} 176 177type ServerObject struct { 178 set *ServerObjectSet 179 index uint64 180} 181 182func (obj ServerObject) String() (string, error) { 183 data := reflect.New(obj.set.resultType).Interface() 184 if err := obj.set.env.server.Get(data, "acvp/v1/"+obj.set.name+"/"+strconv.FormatUint(obj.index, 10)); err != nil { 185 return "", err 186 } 187 ret, err := json.MarshalIndent(data, "", " ") 188 return string(ret), err 189} 190 191func (obj ServerObject) Index(index string) (Object, error) { 192 if obj.set.subObjects == nil { 193 return nil, errors.New("cannot index " + obj.set.name + " objects") 194 } 195 constr, ok := obj.set.subObjects[index] 196 if !ok { 197 return nil, fmt.Errorf("no such subobject %q", index) 198 } 199 return constr(obj.set.env, fmt.Sprintf("%s/%d", obj.set.name, obj.index)) 200} 201 202func (ServerObject) Search(condition acvp.Query) (Object, error) { 203 return nil, errors.New("cannot search individual object") 204} 205 206func edit(initialContents string) ([]byte, error) { 207 tmp, err := os.CreateTemp("", "acvp*.json") 208 if err != nil { 209 return nil, err 210 } 211 path := tmp.Name() 212 defer os.Remove(path) 213 214 _, err = io.WriteString(tmp, initialContents) 215 tmp.Close() 216 if err != nil { 217 return nil, err 218 } 219 220 editor := os.Getenv("EDITOR") 221 if len(editor) == 0 { 222 editor = "vim" 223 } 224 225 cmd := exec.Command(editor, path) 226 cmd.Stdout = os.Stdout 227 cmd.Stdin = os.Stdin 228 cmd.Stderr = os.Stderr 229 if err := cmd.Run(); err != nil { 230 return nil, err 231 } 232 233 return os.ReadFile(path) 234} 235 236func (obj ServerObject) Action(action string, args []string) error { 237 switch action { 238 default: 239 return fmt.Errorf("unknown action %q", action) 240 241 case "edit": 242 if len(args) != 0 { 243 return fmt.Errorf("found %d arguments but %q takes none", len(args), action) 244 } 245 246 contents, err := obj.String() 247 if err != nil { 248 return err 249 } 250 251 newContents, err := edit(contents) 252 if err != nil { 253 return err 254 } 255 256 if trimmed := strings.TrimSpace(string(newContents)); len(trimmed) == 0 || trimmed == strings.TrimSpace(contents) { 257 io.WriteString(obj.set.env.term, "Resulting file was equal or empty. Not updating.\n") 258 return nil 259 } 260 261 var status acvp.RequestStatus 262 if err := obj.set.env.server.Put(&status, "acvp/v1/"+obj.set.name+"/"+strconv.FormatUint(obj.index, 10), newContents); err != nil { 263 return err 264 } 265 266 fmt.Fprintf(obj.set.env.term, "%#v\n", status) 267 return nil 268 269 case "delete": 270 if len(args) != 0 { 271 return fmt.Errorf("found %d arguments but %q takes none", len(args), action) 272 } 273 return obj.set.env.server.Delete("acvp/v1/" + obj.set.name + "/" + strconv.FormatUint(obj.index, 10)) 274 } 275} 276 277type Search struct { 278 ServerObjectSet 279 query acvp.Query 280} 281 282func (search Search) String() (string, error) { 283 data := reflect.New(reflect.SliceOf(search.resultType)).Interface() 284 fmt.Printf("Searching for %#v\n", search.query) 285 if err := search.env.server.GetPaged(data, "acvp/v1/"+search.name, search.query); err != nil { 286 return "", err 287 } 288 ret, err := json.MarshalIndent(data, "", " ") 289 return string(ret), err 290} 291 292func (search Search) Index(_ string) (Object, error) { 293 return nil, errors.New("indexing of search results not supported") 294} 295 296func (search Search) Search(condition acvp.Query) (Object, error) { 297 search.query = append(search.query, condition...) 298 return search, nil 299} 300 301func (Search) Action(_ string, _ []string) error { 302 return errors.New("no actions supported on search objects") 303} 304 305type Algorithms struct { 306 ServerObjectSet 307} 308 309func (algos Algorithms) String() (string, error) { 310 var result struct { 311 Algorithms []map[string]interface{} `json:"algorithms"` 312 } 313 if err := algos.env.server.Get(&result, "acvp/v1/algorithms"); err != nil { 314 return "", err 315 } 316 ret, err := json.MarshalIndent(result.Algorithms, "", " ") 317 return string(ret), err 318} 319 320type Env struct { 321 line string 322 variables map[string]Object 323 server *acvp.Server 324 term *terminal.Terminal 325 config Config 326} 327 328func (e *Env) bytes(node *node32) []byte { 329 return []byte(e.line[node.begin:node.end]) 330} 331 332func (e *Env) contents(node *node32) string { 333 return e.line[node.begin:node.end] 334} 335 336type stringLiteral struct { 337 env *Env 338 contents string 339} 340 341func (s stringLiteral) String() (string, error) { 342 return s.contents, nil 343} 344 345func (stringLiteral) Index(_ string) (Object, error) { 346 return nil, errors.New("cannot index strings") 347} 348 349func (stringLiteral) Search(_ acvp.Query) (Object, error) { 350 return nil, errors.New("cannot search strings") 351} 352 353func (s stringLiteral) Action(action string, args []string) error { 354 switch action { 355 default: 356 return fmt.Errorf("action %q not supported on string literals", action) 357 358 case "GET": 359 if len(args) != 0 { 360 return fmt.Errorf("found %d arguments but %q takes none", len(args), action) 361 } 362 363 var results map[string]interface{} 364 if err := s.env.server.Get(&results, s.contents); err != nil { 365 return err 366 } 367 ret, err := json.MarshalIndent(results, "", " ") 368 if err != nil { 369 return err 370 } 371 s.env.term.Write(ret) 372 return nil 373 } 374} 375 376type results struct { 377 env *Env 378 prefix string 379} 380 381func (r results) String() (string, error) { 382 var results map[string]interface{} 383 if err := r.env.server.Get(&results, "acvp/v1/"+r.prefix+"/results"); err != nil { 384 return "", err 385 } 386 ret, err := json.MarshalIndent(results, "", " ") 387 return string(ret), err 388} 389 390func (results) Index(_ string) (Object, error) { 391 return nil, errors.New("cannot index results objects") 392} 393 394func (results) Search(_ acvp.Query) (Object, error) { 395 return nil, errors.New("cannot search results objects") 396} 397 398func (results) Action(_ string, _ []string) error { 399 return errors.New("no actions supported on results objects") 400} 401 402func (e *Env) parseStringLiteral(node *node32) string { 403 assertNodeType(node, ruleStringLiteral) 404 in := e.bytes(node) 405 var buf bytes.Buffer 406 for i := 1; i < len(in)-1; i++ { 407 if in[i] == '\\' { 408 switch in[i+1] { 409 case '\\': 410 buf.WriteByte('\\') 411 case 'n': 412 buf.WriteByte('\n') 413 case '"': 414 buf.WriteByte('"') 415 default: 416 panic("unknown escape") 417 } 418 i++ 419 continue 420 } 421 buf.WriteByte(in[i]) 422 } 423 424 return buf.String() 425} 426 427func (e *Env) evalExpression(node *node32) (obj Object, err error) { 428 switch node.pegRule { 429 case ruleStringLiteral: 430 return stringLiteral{e, e.parseStringLiteral(node)}, nil 431 432 case ruleVariable: 433 varName := e.contents(node) 434 obj, ok := e.variables[varName] 435 if !ok { 436 return nil, fmt.Errorf("unknown variable %q", varName) 437 } 438 return obj, nil 439 440 case ruleIndexing: 441 node = node.up 442 assertNodeType(node, ruleVariable) 443 varName := e.contents(node) 444 obj, ok := e.variables[varName] 445 if !ok { 446 return nil, fmt.Errorf("unknown variable %q", varName) 447 } 448 449 node = node.next 450 for node != nil { 451 assertNodeType(node, ruleIndex) 452 indexStr := e.contents(node) 453 if obj, err = obj.Index(indexStr); err != nil { 454 return nil, err 455 } 456 node = node.next 457 } 458 459 return obj, nil 460 461 case ruleSearch: 462 node = node.up 463 assertNodeType(node, ruleVariable) 464 varName := e.contents(node) 465 obj, ok := e.variables[varName] 466 if !ok { 467 return nil, fmt.Errorf("unknown variable %q", varName) 468 } 469 470 node = skipWS(node.next) 471 assertNodeType(node, ruleQuery) 472 node = node.up 473 474 var query acvp.Query 475 for node != nil { 476 assertNodeType(node, ruleConjunctions) 477 query = append(query, e.parseConjunction(node.up)) 478 node = skipWS(node.next) 479 } 480 481 if len(query) == 0 { 482 return nil, errors.New("cannot have empty query") 483 } 484 485 return obj.Search(query) 486 } 487 488 panic("unhandled") 489} 490 491func (e *Env) evalAction(node *node32) error { 492 assertNodeType(node, ruleExpression) 493 obj, err := e.evalExpression(node.up) 494 if err != nil { 495 return err 496 } 497 498 node = node.next 499 assertNodeType(node, ruleCommand) 500 node = node.up 501 assertNodeType(node, ruleFunction) 502 function := e.contents(node) 503 node = node.next 504 505 var args []string 506 for node != nil { 507 assertNodeType(node, ruleArgs) 508 node = node.up 509 args = append(args, e.parseStringLiteral(node)) 510 511 node = skipWS(node.next) 512 } 513 514 return obj.Action(function, args) 515} 516 517func (e *Env) parseConjunction(node *node32) (ret acvp.Conjunction) { 518 for node != nil { 519 assertNodeType(node, ruleConjunction) 520 ret = append(ret, e.parseCondition(node.up)) 521 522 node = skipWS(node.next) 523 if node != nil { 524 assertNodeType(node, ruleConjunctions) 525 node = node.up 526 } 527 } 528 return ret 529} 530 531func (e *Env) parseCondition(node *node32) (ret acvp.Condition) { 532 assertNodeType(node, ruleField) 533 ret.Param = e.contents(node) 534 node = skipWS(node.next) 535 536 assertNodeType(node, ruleRelation) 537 switch e.contents(node) { 538 case "==": 539 ret.Relation = acvp.Equals 540 case "!=": 541 ret.Relation = acvp.NotEquals 542 case "contains": 543 ret.Relation = acvp.Contains 544 case "startsWith": 545 ret.Relation = acvp.StartsWith 546 case "endsWith": 547 ret.Relation = acvp.EndsWith 548 default: 549 panic("relation not handled: " + e.contents(node)) 550 } 551 node = skipWS(node.next) 552 553 ret.Value = e.parseStringLiteral(node) 554 555 return ret 556} 557 558func runInteractive(server *acvp.Server, config Config) { 559 oldState, err := terminal.MakeRaw(0) 560 if err != nil { 561 panic(err) 562 } 563 defer terminal.Restore(0, oldState) 564 term := terminal.NewTerminal(os.Stdin, "> ") 565 566 resizeChan := make(chan os.Signal, 1) 567 go func() { 568 for _ = range resizeChan { 569 updateTerminalSize(term) 570 } 571 }() 572 signal.Notify(resizeChan, syscall.SIGWINCH) 573 574 env := &Env{variables: make(map[string]Object), server: server, term: term, config: config} 575 env.variables["requests"] = ServerObjectSet{ 576 env: env, 577 name: "requests", 578 resultType: reflect.TypeOf(&acvp.RequestStatus{}), 579 canEnumerate: true, 580 } 581 env.variables["vendors"] = ServerObjectSet{ 582 env: env, 583 name: "vendors", 584 searchKeys: map[string][]acvp.Relation{ 585 "name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 586 "website": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 587 "email": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 588 "phoneNumber": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 589 }, 590 subObjects: map[string]func(*Env, string) (Object, error){ 591 "contacts": func(env *Env, prefix string) (Object, error) { 592 return ServerObjectSet{ 593 env: env, 594 name: prefix + "/contacts", 595 resultType: reflect.TypeOf(&acvp.Person{}), 596 canEnumerate: true, 597 }, nil 598 }, 599 "addresses": func(env *Env, prefix string) (Object, error) { 600 return ServerObjectSet{ 601 env: env, 602 name: prefix + "/addresses", 603 resultType: reflect.TypeOf(&acvp.Address{}), 604 canEnumerate: true, 605 }, nil 606 }, 607 }, 608 resultType: reflect.TypeOf(&acvp.Vendor{}), 609 } 610 env.variables["persons"] = ServerObjectSet{ 611 env: env, 612 name: "persons", 613 searchKeys: map[string][]acvp.Relation{ 614 "fullName": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 615 "email": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 616 "phoneNumber": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 617 "vendorId": []acvp.Relation{acvp.Equals, acvp.NotEquals, acvp.LessThan, acvp.LessThanEqual, acvp.GreaterThan, acvp.GreaterThanEqual}, 618 }, 619 resultType: reflect.TypeOf(&acvp.Person{}), 620 } 621 env.variables["modules"] = ServerObjectSet{ 622 env: env, 623 name: "modules", 624 searchKeys: map[string][]acvp.Relation{ 625 "name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 626 "version": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 627 "website": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 628 "description": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 629 "type": []acvp.Relation{acvp.Equals, acvp.NotEquals}, 630 "vendorId": []acvp.Relation{acvp.Equals, acvp.NotEquals, acvp.LessThan, acvp.LessThanEqual, acvp.GreaterThan, acvp.GreaterThanEqual}, 631 }, 632 resultType: reflect.TypeOf(&acvp.Module{}), 633 } 634 env.variables["oes"] = ServerObjectSet{ 635 env: env, 636 name: "oes", 637 searchKeys: map[string][]acvp.Relation{ 638 "name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 639 }, 640 resultType: reflect.TypeOf(&acvp.OperationalEnvironment{}), 641 } 642 env.variables["deps"] = ServerObjectSet{ 643 env: env, 644 name: "dependencies", 645 searchKeys: map[string][]acvp.Relation{ 646 "name": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 647 "type": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 648 "description": []acvp.Relation{acvp.Equals, acvp.StartsWith, acvp.EndsWith, acvp.Contains}, 649 }, 650 resultType: reflect.TypeOf(&acvp.Dependency{}), 651 } 652 env.variables["algos"] = Algorithms{ 653 ServerObjectSet{ 654 env: env, 655 name: "algorithms", 656 resultType: reflect.TypeOf(&acvp.Algorithm{}), 657 canEnumerate: true, 658 }, 659 } 660 env.variables["sessions"] = ServerObjectSet{ 661 env: env, 662 name: "testSessions", 663 resultType: reflect.TypeOf(&acvp.TestSession{}), 664 canEnumerate: true, 665 subObjects: map[string]func(env *Env, prefix string) (Object, error){ 666 "results": func(env *Env, prefix string) (Object, error) { 667 return results{env: env, prefix: prefix}, nil 668 }, 669 }, 670 } 671 672 for { 673 if env.line, err = term.ReadLine(); err != nil { 674 return 675 } 676 if len(env.line) == 0 { 677 continue 678 } 679 680 stmt := Statement{Buffer: env.line, Pretty: true} 681 stmt.Init() 682 if err := stmt.Parse(); err != nil { 683 io.WriteString(term, err.Error()) 684 continue 685 } 686 687 node := skipWS(stmt.AST().up) 688 switch node.pegRule { 689 case ruleExpression: 690 obj, err := env.evalExpression(node.up) 691 var repr string 692 if err == nil { 693 repr, err = obj.String() 694 } 695 696 if err != nil { 697 fmt.Fprintf(term, "error while evaluating expression: %s\n", err) 698 } else { 699 io.WriteString(term, repr) 700 io.WriteString(term, "\n") 701 } 702 703 case ruleAction: 704 if err := env.evalAction(node.up); err != nil { 705 io.WriteString(term, err.Error()) 706 io.WriteString(term, "\n") 707 } 708 709 default: 710 fmt.Fprintf(term, "internal error parsing input.\n") 711 } 712 } 713} 714