]>
Commit | Line | Data |
---|---|---|
c680a8e1 RS |
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 | return | |
160 | } |