main
Raw Download raw file
  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
  5package packet
  6
  7import (
  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>"
 15type 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
 21func 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 '>'
 33func 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
 66func (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.
 79func (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>".
 90func 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
159	// RFC 2822 3.4: alternate simple form of a mailbox
160	if email == "" && strings.ContainsRune(name, '@') {
161		email = name
162		name = ""
163	}
164
165	return
166}