...

Source file src/golang.org/x/crypto/openpgp/packet/userid.go

Documentation: golang.org/x/crypto/openpgp/packet

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package packet
     6  
     7  import (
     8  	"io"
     9  	"strings"
    10  )
    11  
    12  // UserId contains text that is intended to represent the name and email
    13  // address of the key holder. See RFC 4880, section 5.11. By convention, this
    14  // takes the form "Full Name (Comment) <email@example.com>"
    15  type UserId struct {
    16  	Id string // By convention, this takes the form "Full Name (Comment) <email@example.com>" which is split out in the fields below.
    17  
    18  	Name, Comment, Email string
    19  }
    20  
    21  func hasInvalidCharacters(s string) bool {
    22  	for _, c := range s {
    23  		switch c {
    24  		case '(', ')', '<', '>', 0:
    25  			return true
    26  		}
    27  	}
    28  	return false
    29  }
    30  
    31  // NewUserId returns a UserId or nil if any of the arguments contain invalid
    32  // characters. The invalid characters are '\x00', '(', ')', '<' and '>'
    33  func NewUserId(name, comment, email string) *UserId {
    34  	// RFC 4880 doesn't deal with the structure of userid strings; the
    35  	// name, comment and email form is just a convention. However, there's
    36  	// no convention about escaping the metacharacters and GPG just refuses
    37  	// to create user ids where, say, the name contains a '('. We mirror
    38  	// this behaviour.
    39  
    40  	if hasInvalidCharacters(name) || hasInvalidCharacters(comment) || hasInvalidCharacters(email) {
    41  		return nil
    42  	}
    43  
    44  	uid := new(UserId)
    45  	uid.Name, uid.Comment, uid.Email = name, comment, email
    46  	uid.Id = name
    47  	if len(comment) > 0 {
    48  		if len(uid.Id) > 0 {
    49  			uid.Id += " "
    50  		}
    51  		uid.Id += "("
    52  		uid.Id += comment
    53  		uid.Id += ")"
    54  	}
    55  	if len(email) > 0 {
    56  		if len(uid.Id) > 0 {
    57  			uid.Id += " "
    58  		}
    59  		uid.Id += "<"
    60  		uid.Id += email
    61  		uid.Id += ">"
    62  	}
    63  	return uid
    64  }
    65  
    66  func (uid *UserId) parse(r io.Reader) (err error) {
    67  	// RFC 4880, section 5.11
    68  	b, err := io.ReadAll(r)
    69  	if err != nil {
    70  		return
    71  	}
    72  	uid.Id = string(b)
    73  	uid.Name, uid.Comment, uid.Email = parseUserId(uid.Id)
    74  	return
    75  }
    76  
    77  // Serialize marshals uid to w in the form of an OpenPGP packet, including
    78  // header.
    79  func (uid *UserId) Serialize(w io.Writer) error {
    80  	err := serializeHeader(w, packetTypeUserId, len(uid.Id))
    81  	if err != nil {
    82  		return err
    83  	}
    84  	_, err = w.Write([]byte(uid.Id))
    85  	return err
    86  }
    87  
    88  // parseUserId extracts the name, comment and email from a user id string that
    89  // is formatted as "Full Name (Comment) <email@example.com>".
    90  func parseUserId(id string) (name, comment, email string) {
    91  	var n, c, e struct {
    92  		start, end int
    93  	}
    94  	var state int
    95  
    96  	for offset, rune := range id {
    97  		switch state {
    98  		case 0:
    99  			// Entering name
   100  			n.start = offset
   101  			state = 1
   102  			fallthrough
   103  		case 1:
   104  			// In name
   105  			if rune == '(' {
   106  				state = 2
   107  				n.end = offset
   108  			} else if rune == '<' {
   109  				state = 5
   110  				n.end = offset
   111  			}
   112  		case 2:
   113  			// Entering comment
   114  			c.start = offset
   115  			state = 3
   116  			fallthrough
   117  		case 3:
   118  			// In comment
   119  			if rune == ')' {
   120  				state = 4
   121  				c.end = offset
   122  			}
   123  		case 4:
   124  			// Between comment and email
   125  			if rune == '<' {
   126  				state = 5
   127  			}
   128  		case 5:
   129  			// Entering email
   130  			e.start = offset
   131  			state = 6
   132  			fallthrough
   133  		case 6:
   134  			// In email
   135  			if rune == '>' {
   136  				state = 7
   137  				e.end = offset
   138  			}
   139  		default:
   140  			// After email
   141  		}
   142  	}
   143  	switch state {
   144  	case 1:
   145  		// ended in the name
   146  		n.end = len(id)
   147  	case 3:
   148  		// ended in comment
   149  		c.end = len(id)
   150  	case 6:
   151  		// ended in email
   152  		e.end = len(id)
   153  	}
   154  
   155  	name = strings.TrimSpace(id[n.start:n.end])
   156  	comment = strings.TrimSpace(id[c.start:c.end])
   157  	email = strings.TrimSpace(id[e.start:e.end])
   158  	return
   159  }
   160  

View as plain text