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 "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>" 16type 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 22func 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 '>' 34func 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 67func (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. 80func (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>". 91func 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} 161