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