github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/pvl/parse.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  	"encoding/json"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/keybase/client/go/protocol/keybase1"
    12  )
    13  
    14  func parse(in string) (pvlT, error) {
    15  	b := []byte(in)
    16  	p := pvlT{}
    17  	p.PvlVersion = -1
    18  	p.Revision = -1
    19  
    20  	err := json.Unmarshal(b, &p)
    21  	if err != nil {
    22  		return p, err
    23  	}
    24  
    25  	if p.PvlVersion == -1 {
    26  		return p, fmt.Errorf("pvl_version required")
    27  	}
    28  	if p.Revision == -1 {
    29  		return p, fmt.Errorf("revision required")
    30  	}
    31  	return p, nil
    32  }
    33  
    34  type pvlT struct {
    35  	PvlVersion int `json:"pvl_version"`
    36  	Revision   int `json:"revision"`
    37  	// services is a map from service to a list of scripts.
    38  	// each script is a list of instructions.
    39  	Services servicesT `json:"services"`
    40  }
    41  
    42  type servicesT struct {
    43  	Map map[keybase1.ProofType][]scriptT
    44  }
    45  
    46  func (x *servicesT) UnmarshalJSON(b []byte) error {
    47  	// read as string map
    48  	m := make(map[string][]scriptT)
    49  	err := json.Unmarshal(b, &m)
    50  	if err != nil {
    51  		return err
    52  	}
    53  	// copy to ProofType map
    54  	x.Map = make(map[keybase1.ProofType][]scriptT)
    55  	for k, v := range m {
    56  		t, ok := keybase1.ProofTypeMap[strings.ToUpper(k)]
    57  		if ok {
    58  			x.Map[t] = v
    59  		}
    60  		// Unrecognized proof types are discarded silently
    61  		// So that old clients don't break if a new service is added
    62  	}
    63  	return nil
    64  }
    65  
    66  type scriptT struct {
    67  	Instructions []instructionT
    68  }
    69  
    70  func (x *scriptT) UnmarshalJSON(b []byte) error {
    71  	err := json.Unmarshal(b, &x.Instructions)
    72  	for i, ins := range x.Instructions {
    73  		n := ins.variantsFilled()
    74  		if n != 1 {
    75  			if i == 0 {
    76  				return fmt.Errorf("%v != 1 variants appeared in instruction %v", n, i)
    77  			}
    78  			return fmt.Errorf("%v != 1 variants appeared in instruction %v; previous: %v", n, i, x.Instructions[i-1])
    79  		}
    80  	}
    81  	return err
    82  }
    83  
    84  type instructionT struct {
    85  	// Exactly one of these shall be non-nil
    86  	// This list is duplicated to:
    87  	// - instructionT.variantsFilled
    88  	// - instructionT.Name
    89  	// - stepInstruction
    90  	// - validateScript
    91  	// This invariant is enforced by scriptT.UnmarshalJSON
    92  	AssertRegexMatch    *assertRegexMatchT    `json:"assert_regex_match,omitempty"`
    93  	AssertFindBase64    *assertFindBase64T    `json:"assert_find_base64,omitempty"`
    94  	AssertCompare       *assertCompareT       `json:"assert_compare,omitempty"`
    95  	WhitespaceNormalize *whitespaceNormalizeT `json:"whitespace_normalize,omitempty"`
    96  	RegexCapture        *regexCaptureT        `json:"regex_capture,omitempty"`
    97  	ReplaceAll          *replaceAllT          `json:"replace_all,omitempty"`
    98  	ParseURL            *parseURLT            `json:"parse_url,omitempty"`
    99  	Fetch               *fetchT               `json:"fetch,omitempty"`
   100  	ParseHTML           *parseHTMLT           `json:"parse_html,omitempty"`
   101  	SelectorJSON        *selectorJSONT        `json:"selector_json,omitempty"`
   102  	SelectorCSS         *selectorCSST         `json:"selector_css,omitempty"`
   103  	Fill                *fillT                `json:"fill,omitempty"`
   104  }
   105  
   106  func (ins *instructionT) variantsFilled() int {
   107  	n := 0
   108  	if ins.AssertRegexMatch != nil {
   109  		n++
   110  	}
   111  	if ins.AssertFindBase64 != nil {
   112  		n++
   113  	}
   114  	if ins.AssertCompare != nil {
   115  		n++
   116  	}
   117  	if ins.WhitespaceNormalize != nil {
   118  		n++
   119  	}
   120  	if ins.RegexCapture != nil {
   121  		n++
   122  	}
   123  	if ins.ReplaceAll != nil {
   124  		n++
   125  	}
   126  	if ins.ParseURL != nil {
   127  		n++
   128  	}
   129  	if ins.Fetch != nil {
   130  		n++
   131  	}
   132  	if ins.ParseHTML != nil {
   133  		n++
   134  	}
   135  	if ins.SelectorJSON != nil {
   136  		n++
   137  	}
   138  	if ins.SelectorCSS != nil {
   139  		n++
   140  	}
   141  	if ins.Fill != nil {
   142  		n++
   143  	}
   144  	return n
   145  }
   146  
   147  func (ins instructionT) Name() string {
   148  	switch {
   149  	case ins.AssertRegexMatch != nil:
   150  		return string(cmdAssertRegexMatch)
   151  	case ins.AssertFindBase64 != nil:
   152  		return string(cmdAssertFindBase64)
   153  	case ins.AssertCompare != nil:
   154  		return string(cmdAssertCompare)
   155  	case ins.WhitespaceNormalize != nil:
   156  		return string(cmdWhitespaceNormalize)
   157  	case ins.RegexCapture != nil:
   158  		return string(cmdRegexCapture)
   159  	case ins.ReplaceAll != nil:
   160  		return string(cmdReplaceAll)
   161  	case ins.ParseURL != nil:
   162  		return string(cmdParseURL)
   163  	case ins.Fetch != nil:
   164  		return string(cmdFetch)
   165  	case ins.ParseHTML != nil:
   166  		return string(cmdParseHTML)
   167  	case ins.SelectorJSON != nil:
   168  		return string(cmdSelectorJSON)
   169  	case ins.SelectorCSS != nil:
   170  		return string(cmdSelectorCSS)
   171  	case ins.Fill != nil:
   172  		return string(cmdFill)
   173  	}
   174  	return "<invalid instruction>"
   175  }
   176  
   177  func (ins instructionT) String() string {
   178  	return fmt.Sprintf("[ins %v]", ins.Name())
   179  }
   180  
   181  type assertRegexMatchT struct {
   182  	Pattern         string  `json:"pattern"`
   183  	CaseInsensitive bool    `json:"case_insensitive"`
   184  	MultiLine       bool    `json:"multiline"`
   185  	From            string  `json:"from"`
   186  	Negate          bool    `json:"negate"`
   187  	Error           *errorT `json:"error"`
   188  }
   189  
   190  type assertFindBase64T struct {
   191  	Needle   string  `json:"needle"`
   192  	Haystack string  `json:"haystack"`
   193  	Error    *errorT `json:"error"`
   194  }
   195  
   196  type assertCompareT struct {
   197  	// Comparison strategy
   198  	Cmp   string  `json:"cmp"`
   199  	A     string  `json:"a"`
   200  	B     string  `json:"b"`
   201  	Error *errorT `json:"error"`
   202  }
   203  
   204  type whitespaceNormalizeT struct {
   205  	From  string  `json:"from"`
   206  	Into  string  `json:"into"`
   207  	Error *errorT `json:"error"`
   208  }
   209  
   210  type regexCaptureT struct {
   211  	Pattern         string   `json:"pattern"`
   212  	MultiLine       bool     `json:"multiline"`
   213  	CaseInsensitive bool     `json:"case_insensitive"`
   214  	From            string   `json:"from"`
   215  	Into            []string `json:"into"`
   216  	Error           *errorT  `json:"error"`
   217  }
   218  
   219  type replaceAllT struct {
   220  	Old   string  `json:"old"`
   221  	New   string  `json:"new"`
   222  	From  string  `json:"from"`
   223  	Into  string  `json:"into"`
   224  	Error *errorT `json:"error"`
   225  }
   226  
   227  type parseURLT struct {
   228  	From   string  `json:"from"`
   229  	Path   string  `json:"path"`
   230  	Host   string  `json:"host"`
   231  	Scheme string  `json:"scheme"`
   232  	Error  *errorT `json:"error"`
   233  }
   234  
   235  type fetchT struct {
   236  	Kind string `json:"kind"`
   237  	From string `json:"from"`
   238  	// Value is "" when not fetching a string
   239  	Into  string  `json:"into"`
   240  	Error *errorT `json:"error"`
   241  }
   242  
   243  type parseHTMLT struct {
   244  	From  string  `json:"from"`
   245  	Error *errorT `json:"error"`
   246  }
   247  
   248  type selectorJSONT struct {
   249  	Selectors []keybase1.SelectorEntry `json:"selectors"`
   250  	Into      string                   `json:"into"`
   251  	Error     *errorT                  `json:"error"`
   252  }
   253  
   254  type selectorCSST struct {
   255  	Selectors []keybase1.SelectorEntry `json:"selectors"`
   256  	Attr      string                   `json:"attr"`
   257  	Data      bool                     `json:"data"`
   258  	// Whether the final selection can contain multiple elements.
   259  	Multi bool    `json:"multi"`
   260  	Into  string  `json:"into"`
   261  	Error *errorT `json:"error"`
   262  }
   263  
   264  type fillT struct {
   265  	With  string  `json:"with"`
   266  	Into  string  `json:"into"`
   267  	Error *errorT `json:"error"`
   268  }
   269  
   270  type errorT struct {
   271  	Status      keybase1.ProofStatus
   272  	Description string
   273  }
   274  
   275  func (e *errorT) UnmarshalJSON(b []byte) error {
   276  	ss := []string{}
   277  	err := json.Unmarshal(b, &ss)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	if len(ss) != 2 {
   282  		return fmt.Errorf("error desc must be of length 2")
   283  	}
   284  	status, ok := keybase1.ProofStatusMap[ss[0]]
   285  	if !ok {
   286  		return fmt.Errorf("unrecognized proof status '%v'", ss[0])
   287  	}
   288  	e.Status = status
   289  	e.Description = ss[1]
   290  	return nil
   291  }