main
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}