| // Copyright 2011 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package packet |
| |
| import ( |
| "io" |
| "io/ioutil" |
| "strings" |
| ) |
| |
| // UserId contains text that is intended to represent the name and email |
| // address of the key holder. See RFC 4880, section 5.11. By convention, this |
| // takes the form "Full Name (Comment) <email@example.com>" |
| type UserId struct { |
| Id string // By convention, this takes the form "Full Name (Comment) <email@example.com>" which is split out in the fields below. |
| |
| Name, Comment, Email string |
| } |
| |
| func hasInvalidCharacters(s string) bool { |
| for _, c := range s { |
| switch c { |
| case '(', ')', '<', '>', 0: |
| return true |
| } |
| } |
| return false |
| } |
| |
| // NewUserId returns a UserId or nil if any of the arguments contain invalid |
| // characters. The invalid characters are '\x00', '(', ')', '<' and '>' |
| func NewUserId(name, comment, email string) *UserId { |
| // RFC 4880 doesn't deal with the structure of userid strings; the |
| // name, comment and email form is just a convention. However, there's |
| // no convention about escaping the metacharacters and GPG just refuses |
| // to create user ids where, say, the name contains a '('. We mirror |
| // this behaviour. |
| |
| if hasInvalidCharacters(name) || hasInvalidCharacters(comment) || hasInvalidCharacters(email) { |
| return nil |
| } |
| |
| uid := new(UserId) |
| uid.Name, uid.Comment, uid.Email = name, comment, email |
| uid.Id = name |
| if len(comment) > 0 { |
| if len(uid.Id) > 0 { |
| uid.Id += " " |
| } |
| uid.Id += "(" |
| uid.Id += comment |
| uid.Id += ")" |
| } |
| if len(email) > 0 { |
| if len(uid.Id) > 0 { |
| uid.Id += " " |
| } |
| uid.Id += "<" |
| uid.Id += email |
| uid.Id += ">" |
| } |
| return uid |
| } |
| |
| func (uid *UserId) parse(r io.Reader) (err error) { |
| // RFC 4880, section 5.11 |
| b, err := ioutil.ReadAll(r) |
| if err != nil { |
| return |
| } |
| uid.Id = string(b) |
| uid.Name, uid.Comment, uid.Email = parseUserId(uid.Id) |
| return |
| } |
| |
| // Serialize marshals uid to w in the form of an OpenPGP packet, including |
| // header. |
| func (uid *UserId) Serialize(w io.Writer) error { |
| err := serializeHeader(w, packetTypeUserId, len(uid.Id)) |
| if err != nil { |
| return err |
| } |
| _, err = w.Write([]byte(uid.Id)) |
| return err |
| } |
| |
| // parseUserId extracts the name, comment and email from a user id string that |
| // is formatted as "Full Name (Comment) <email@example.com>". |
| func parseUserId(id string) (name, comment, email string) { |
| var n, c, e struct { |
| start, end int |
| } |
| var state int |
| |
| for offset, rune := range id { |
| switch state { |
| case 0: |
| // Entering name |
| n.start = offset |
| state = 1 |
| fallthrough |
| case 1: |
| // In name |
| if rune == '(' { |
| state = 2 |
| n.end = offset |
| } else if rune == '<' { |
| state = 5 |
| n.end = offset |
| } |
| case 2: |
| // Entering comment |
| c.start = offset |
| state = 3 |
| fallthrough |
| case 3: |
| // In comment |
| if rune == ')' { |
| state = 4 |
| c.end = offset |
| } |
| case 4: |
| // Between comment and email |
| if rune == '<' { |
| state = 5 |
| } |
| case 5: |
| // Entering email |
| e.start = offset |
| state = 6 |
| fallthrough |
| case 6: |
| // In email |
| if rune == '>' { |
| state = 7 |
| e.end = offset |
| } |
| default: |
| // After email |
| } |
| } |
| switch state { |
| case 1: |
| // ended in the name |
| n.end = len(id) |
| case 3: |
| // ended in comment |
| c.end = len(id) |
| case 6: |
| // ended in email |
| e.end = len(id) |
| } |
| |
| name = strings.TrimSpace(id[n.start:n.end]) |
| comment = strings.TrimSpace(id[c.start:c.end]) |
| email = strings.TrimSpace(id[e.start:e.end]) |
| return |
| } |