Russ Cox | 470549d | 2012-01-25 15:31:12 -0500 | [diff] [blame] | 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 | } |