...

Source file src/github.com/ProtonMail/go-crypto/openpgp/packet/userid.go

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

View as plain text