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