message.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  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 mail implements parsing of mail messages.
  6. For the most part, this package follows the syntax as specified by RFC 5322 and
  7. extended by RFC 6532.
  8. Notable divergences:
  9. * Obsolete address formats are not parsed, including addresses with
  10. embedded route information.
  11. * The full range of spacing (the CFWS syntax element) is not supported,
  12. such as breaking addresses across lines.
  13. * No unicode normalization is performed.
  14. * The special characters ()[]:;@\, are allowed to appear unquoted in names.
  15. */
  16. package mail
  17. import (
  18. "bufio"
  19. "errors"
  20. "fmt"
  21. "io"
  22. "log"
  23. "mime"
  24. "net/textproto"
  25. "strings"
  26. "sync"
  27. "time"
  28. "unicode/utf8"
  29. )
  30. var debug = debugT(false)
  31. type debugT bool
  32. func (d debugT) Printf(format string, args ...any) {
  33. if d {
  34. log.Printf(format, args...)
  35. }
  36. }
  37. // A Message represents a parsed mail message.
  38. type Message struct {
  39. Header Header
  40. Body io.Reader
  41. }
  42. // ReadMessage reads a message from r.
  43. // The headers are parsed, and the body of the message will be available
  44. // for reading from msg.Body.
  45. func ReadMessage(r io.Reader) (msg *Message, err error) {
  46. tp := textproto.NewReader(bufio.NewReader(r))
  47. hdr, err := tp.ReadMIMEHeader()
  48. if err != nil {
  49. return nil, err
  50. }
  51. return &Message{
  52. Header: Header(hdr),
  53. Body: tp.R,
  54. }, nil
  55. }
  56. // Layouts suitable for passing to time.Parse.
  57. // These are tried in order.
  58. var (
  59. dateLayoutsBuildOnce sync.Once
  60. dateLayouts []string
  61. )
  62. func buildDateLayouts() {
  63. // Generate layouts based on RFC 5322, section 3.3.
  64. dows := [...]string{"", "Mon, "} // day-of-week
  65. days := [...]string{"2", "02"} // day = 1*2DIGIT
  66. years := [...]string{"2006", "06"} // year = 4*DIGIT / 2*DIGIT
  67. seconds := [...]string{":05", ""} // second
  68. // "-0700 (MST)" is not in RFC 5322, but is common.
  69. zones := [...]string{"-0700", "MST"} // zone = (("+" / "-") 4DIGIT) / "GMT" / ...
  70. for _, dow := range dows {
  71. for _, day := range days {
  72. for _, year := range years {
  73. for _, second := range seconds {
  74. for _, zone := range zones {
  75. s := dow + day + " Jan " + year + " 15:04" + second + " " + zone
  76. dateLayouts = append(dateLayouts, s)
  77. }
  78. }
  79. }
  80. }
  81. }
  82. }
  83. // ParseDate parses an RFC 5322 date string.
  84. func ParseDate(date string) (time.Time, error) {
  85. dateLayoutsBuildOnce.Do(buildDateLayouts)
  86. // CR and LF must match and are tolerated anywhere in the date field.
  87. date = strings.ReplaceAll(date, "\r\n", "")
  88. if strings.Contains(date, "\r") {
  89. return time.Time{}, errors.New("mail: header has a CR without LF")
  90. }
  91. // Re-using some addrParser methods which support obsolete text, i.e. non-printable ASCII
  92. p := addrParser{date, nil}
  93. p.skipSpace()
  94. // RFC 5322: zone = (FWS ( "+" / "-" ) 4DIGIT) / obs-zone
  95. // zone length is always 5 chars unless obsolete (obs-zone)
  96. if ind := strings.IndexAny(p.s, "+-"); ind != -1 && len(p.s) >= ind+5 {
  97. date = p.s[:ind+5]
  98. p.s = p.s[ind+5:]
  99. } else {
  100. ind := strings.Index(p.s, "T")
  101. if ind == 0 {
  102. // In this case we have the following date formats:
  103. // * Thu, 20 Nov 1997 09:55:06 MDT
  104. // * Thu, 20 Nov 1997 09:55:06 MDT (MDT)
  105. // * Thu, 20 Nov 1997 09:55:06 MDT (This comment)
  106. ind = strings.Index(p.s[1:], "T")
  107. if ind != -1 {
  108. ind++
  109. }
  110. }
  111. if ind != -1 && len(p.s) >= ind+5 {
  112. // The last letter T of the obsolete time zone is checked when no standard time zone is found.
  113. // If T is misplaced, the date to parse is garbage.
  114. date = p.s[:ind+1]
  115. p.s = p.s[ind+1:]
  116. }
  117. }
  118. if !p.skipCFWS() {
  119. return time.Time{}, errors.New("mail: misformatted parenthetical comment")
  120. }
  121. for _, layout := range dateLayouts {
  122. t, err := time.Parse(layout, date)
  123. if err == nil {
  124. return t, nil
  125. }
  126. }
  127. return time.Time{}, errors.New("mail: header could not be parsed")
  128. }
  129. // A Header represents the key-value pairs in a mail message header.
  130. type Header map[string][]string
  131. // Get gets the first value associated with the given key.
  132. // It is case insensitive; CanonicalMIMEHeaderKey is used
  133. // to canonicalize the provided key.
  134. // If there are no values associated with the key, Get returns "".
  135. // To access multiple values of a key, or to use non-canonical keys,
  136. // access the map directly.
  137. func (h Header) Get(key string) string {
  138. return textproto.MIMEHeader(h).Get(key)
  139. }
  140. var ErrHeaderNotPresent = errors.New("mail: header not in message")
  141. // Date parses the Date header field.
  142. func (h Header) Date() (time.Time, error) {
  143. hdr := h.Get("Date")
  144. if hdr == "" {
  145. return time.Time{}, ErrHeaderNotPresent
  146. }
  147. return ParseDate(hdr)
  148. }
  149. // AddressList parses the named header field as a list of addresses.
  150. func (h Header) AddressList(key string) ([]*Address, error) {
  151. hdr := h.Get(key)
  152. if hdr == "" {
  153. return nil, ErrHeaderNotPresent
  154. }
  155. return ParseAddressList(hdr)
  156. }
  157. // Address represents a single mail address.
  158. // An address such as "Barry Gibbs <bg@example.com>" is represented
  159. // as Address{Name: "Barry Gibbs", Address: "bg@example.com"}.
  160. type Address struct {
  161. Name string // Proper name; may be empty.
  162. Address string // user@domain
  163. }
  164. // ParseAddress parses a single RFC 5322 address, e.g. "Barry Gibbs <bg@example.com>"
  165. func ParseAddress(address string) (*Address, error) {
  166. return (&addrParser{s: address}).parseSingleAddress()
  167. }
  168. // ParseAddressList parses the given string as a list of addresses.
  169. func ParseAddressList(list string) ([]*Address, error) {
  170. return (&addrParser{s: list}).parseAddressList()
  171. }
  172. // An AddressParser is an RFC 5322 address parser.
  173. type AddressParser struct {
  174. // WordDecoder optionally specifies a decoder for RFC 2047 encoded-words.
  175. WordDecoder *mime.WordDecoder
  176. }
  177. // Parse parses a single RFC 5322 address of the
  178. // form "Gogh Fir <gf@example.com>" or "foo@example.com".
  179. func (p *AddressParser) Parse(address string) (*Address, error) {
  180. return (&addrParser{s: address, dec: p.WordDecoder}).parseSingleAddress()
  181. }
  182. // ParseList parses the given string as a list of comma-separated addresses
  183. // of the form "Gogh Fir <gf@example.com>" or "foo@example.com".
  184. func (p *AddressParser) ParseList(list string) ([]*Address, error) {
  185. return (&addrParser{s: list, dec: p.WordDecoder}).parseAddressList()
  186. }
  187. // String formats the address as a valid RFC 5322 address.
  188. // If the address's name contains non-ASCII characters
  189. // the name will be rendered according to RFC 2047.
  190. func (a *Address) String() string {
  191. // Format address local@domain
  192. at := strings.LastIndex(a.Address, "@")
  193. var local, domain string
  194. if at < 0 {
  195. // This is a malformed address ("@" is required in addr-spec);
  196. // treat the whole address as local-part.
  197. local = a.Address
  198. } else {
  199. local, domain = a.Address[:at], a.Address[at+1:]
  200. }
  201. // Add quotes if needed
  202. quoteLocal := false
  203. for i, r := range local {
  204. if isAtext(r, false, false) {
  205. continue
  206. }
  207. if r == '.' {
  208. // Dots are okay if they are surrounded by atext.
  209. // We only need to check that the previous byte is
  210. // not a dot, and this isn't the end of the string.
  211. if i > 0 && local[i-1] != '.' && i < len(local)-1 {
  212. continue
  213. }
  214. }
  215. quoteLocal = true
  216. break
  217. }
  218. if quoteLocal {
  219. local = quoteString(local)
  220. }
  221. s := "<" + local + "@" + domain + ">"
  222. if a.Name == "" {
  223. return s
  224. }
  225. // If every character is printable ASCII, quoting is simple.
  226. allPrintable := true
  227. for _, r := range a.Name {
  228. // isWSP here should actually be isFWS,
  229. // but we don't support folding yet.
  230. if !isVchar(r) && !isWSP(r) || isMultibyte(r) {
  231. allPrintable = false
  232. break
  233. }
  234. }
  235. if allPrintable {
  236. return quoteString(a.Name) + " " + s
  237. }
  238. // Text in an encoded-word in a display-name must not contain certain
  239. // characters like quotes or parentheses (see RFC 2047 section 5.3).
  240. // When this is the case encode the name using base64 encoding.
  241. if strings.ContainsAny(a.Name, "\"#$%&'(),.:;<>@[]^`{|}~") {
  242. return mime.BEncoding.Encode("utf-8", a.Name) + " " + s
  243. }
  244. return mime.QEncoding.Encode("utf-8", a.Name) + " " + s
  245. }
  246. type addrParser struct {
  247. s string
  248. dec *mime.WordDecoder // may be nil
  249. }
  250. func (p *addrParser) parseAddressList() ([]*Address, error) {
  251. var list []*Address
  252. for {
  253. p.skipSpace()
  254. // allow skipping empty entries (RFC5322 obs-addr-list)
  255. if p.consume(',') {
  256. continue
  257. }
  258. addrs, err := p.parseAddress(true)
  259. if err != nil {
  260. return nil, err
  261. }
  262. list = append(list, addrs...)
  263. if !p.skipCFWS() {
  264. return nil, errors.New("mail: misformatted parenthetical comment")
  265. }
  266. if p.empty() {
  267. break
  268. }
  269. if p.peek() != ',' {
  270. return nil, errors.New("mail: expected comma")
  271. }
  272. // Skip empty entries for obs-addr-list.
  273. for p.consume(',') {
  274. p.skipSpace()
  275. }
  276. if p.empty() {
  277. break
  278. }
  279. }
  280. return list, nil
  281. }
  282. func (p *addrParser) parseSingleAddress() (*Address, error) {
  283. addrs, err := p.parseAddress(true)
  284. if err != nil {
  285. return nil, err
  286. }
  287. if !p.skipCFWS() {
  288. return nil, errors.New("mail: misformatted parenthetical comment")
  289. }
  290. if !p.empty() {
  291. return nil, fmt.Errorf("mail: expected single address, got %q", p.s)
  292. }
  293. if len(addrs) == 0 {
  294. return nil, errors.New("mail: empty group")
  295. }
  296. if len(addrs) > 1 {
  297. return nil, errors.New("mail: group with multiple addresses")
  298. }
  299. return addrs[0], nil
  300. }
  301. // parseAddress parses a single RFC 5322 address at the start of p.
  302. func (p *addrParser) parseAddress(handleGroup bool) ([]*Address, error) {
  303. debug.Printf("parseAddress: %q", p.s)
  304. p.skipSpace()
  305. if p.empty() {
  306. return nil, errors.New("mail: no address")
  307. }
  308. // address = mailbox / group
  309. // mailbox = name-addr / addr-spec
  310. // group = display-name ":" [group-list] ";" [CFWS]
  311. // addr-spec has a more restricted grammar than name-addr,
  312. // so try parsing it first, and fallback to name-addr.
  313. // TODO(dsymonds): Is this really correct?
  314. spec, err := p.consumeAddrSpec()
  315. if err == nil {
  316. var displayName string
  317. p.skipSpace()
  318. if !p.empty() && p.peek() == '(' {
  319. displayName, err = p.consumeDisplayNameComment()
  320. if err != nil {
  321. return nil, err
  322. }
  323. }
  324. return []*Address{{
  325. Name: displayName,
  326. Address: spec,
  327. }}, err
  328. }
  329. debug.Printf("parseAddress: not an addr-spec: %v", err)
  330. debug.Printf("parseAddress: state is now %q", p.s)
  331. // display-name
  332. var displayName string
  333. if p.peek() != '<' {
  334. displayName, err = p.consumePhrase()
  335. if err != nil {
  336. return nil, err
  337. }
  338. }
  339. debug.Printf("parseAddress: displayName=%q", displayName)
  340. p.skipSpace()
  341. if handleGroup {
  342. if p.consume(':') {
  343. return p.consumeGroupList()
  344. }
  345. }
  346. // angle-addr = "<" addr-spec ">"
  347. if !p.consume('<') {
  348. atext := true
  349. for _, r := range displayName {
  350. if !isAtext(r, true, false) {
  351. atext = false
  352. break
  353. }
  354. }
  355. if atext {
  356. // The input is like "foo.bar"; it's possible the input
  357. // meant to be "foo.bar@domain", or "foo.bar <...>".
  358. return nil, errors.New("mail: missing '@' or angle-addr")
  359. }
  360. // The input is like "Full Name", which couldn't possibly be a
  361. // valid email address if followed by "@domain"; the input
  362. // likely meant to be "Full Name <...>".
  363. return nil, errors.New("mail: no angle-addr")
  364. }
  365. spec, err = p.consumeAddrSpec()
  366. if err != nil {
  367. return nil, err
  368. }
  369. if !p.consume('>') {
  370. return nil, errors.New("mail: unclosed angle-addr")
  371. }
  372. debug.Printf("parseAddress: spec=%q", spec)
  373. return []*Address{{
  374. Name: displayName,
  375. Address: spec,
  376. }}, nil
  377. }
  378. func (p *addrParser) consumeGroupList() ([]*Address, error) {
  379. var group []*Address
  380. // handle empty group.
  381. p.skipSpace()
  382. if p.consume(';') {
  383. p.skipCFWS()
  384. return group, nil
  385. }
  386. for {
  387. p.skipSpace()
  388. // embedded groups not allowed.
  389. addrs, err := p.parseAddress(false)
  390. if err != nil {
  391. return nil, err
  392. }
  393. group = append(group, addrs...)
  394. if !p.skipCFWS() {
  395. return nil, errors.New("mail: misformatted parenthetical comment")
  396. }
  397. if p.consume(';') {
  398. p.skipCFWS()
  399. break
  400. }
  401. if !p.consume(',') {
  402. return nil, errors.New("mail: expected comma")
  403. }
  404. }
  405. return group, nil
  406. }
  407. // consumeAddrSpec parses a single RFC 5322 addr-spec at the start of p.
  408. func (p *addrParser) consumeAddrSpec() (spec string, err error) {
  409. debug.Printf("consumeAddrSpec: %q", p.s)
  410. orig := *p
  411. defer func() {
  412. if err != nil {
  413. *p = orig
  414. }
  415. }()
  416. // local-part = dot-atom / quoted-string
  417. var localPart string
  418. p.skipSpace()
  419. if p.empty() {
  420. return "", errors.New("mail: no addr-spec")
  421. }
  422. if p.peek() == '"' {
  423. // quoted-string
  424. debug.Printf("consumeAddrSpec: parsing quoted-string")
  425. localPart, err = p.consumeQuotedString()
  426. if localPart == "" {
  427. err = errors.New("mail: empty quoted string in addr-spec")
  428. }
  429. } else {
  430. // dot-atom
  431. debug.Printf("consumeAddrSpec: parsing dot-atom")
  432. localPart, err = p.consumeAtom(true, false)
  433. }
  434. if err != nil {
  435. debug.Printf("consumeAddrSpec: failed: %v", err)
  436. return "", err
  437. }
  438. if !p.consume('@') {
  439. return "", errors.New("mail: missing @ in addr-spec")
  440. }
  441. // domain = dot-atom / domain-literal
  442. var domain string
  443. p.skipSpace()
  444. if p.empty() {
  445. return "", errors.New("mail: no domain in addr-spec")
  446. }
  447. // TODO(dsymonds): Handle domain-literal
  448. domain, err = p.consumeAtom(true, false)
  449. if err != nil {
  450. return "", err
  451. }
  452. return localPart + "@" + domain, nil
  453. }
  454. // consumePhrase parses the RFC 5322 phrase at the start of p.
  455. func (p *addrParser) consumePhrase() (phrase string, err error) {
  456. debug.Printf("consumePhrase: [%s]", p.s)
  457. // phrase = 1*word
  458. var words []string
  459. var isPrevEncoded bool
  460. for {
  461. // word = atom / quoted-string
  462. var word string
  463. p.skipSpace()
  464. if p.empty() {
  465. break
  466. }
  467. isEncoded := false
  468. if p.peek() == '"' {
  469. // quoted-string
  470. word, err = p.consumeQuotedString()
  471. } else {
  472. // atom
  473. // We actually parse dot-atom here to be more permissive
  474. // than what RFC 5322 specifies.
  475. word, err = p.consumeAtom(true, true)
  476. if err == nil {
  477. word, isEncoded, err = p.decodeRFC2047Word(word)
  478. }
  479. }
  480. if err != nil {
  481. break
  482. }
  483. debug.Printf("consumePhrase: consumed %q", word)
  484. if isPrevEncoded && isEncoded {
  485. words[len(words)-1] += word
  486. } else {
  487. words = append(words, word)
  488. }
  489. isPrevEncoded = isEncoded
  490. }
  491. // Ignore any error if we got at least one word.
  492. if err != nil && len(words) == 0 {
  493. debug.Printf("consumePhrase: hit err: %v", err)
  494. return "", fmt.Errorf("mail: missing word in phrase: %v", err)
  495. }
  496. phrase = strings.Join(words, " ")
  497. return phrase, nil
  498. }
  499. // consumeQuotedString parses the quoted string at the start of p.
  500. func (p *addrParser) consumeQuotedString() (qs string, err error) {
  501. // Assume first byte is '"'.
  502. i := 1
  503. qsb := make([]rune, 0, 10)
  504. escaped := false
  505. Loop:
  506. for {
  507. r, size := utf8.DecodeRuneInString(p.s[i:])
  508. switch {
  509. case size == 0:
  510. return "", errors.New("mail: unclosed quoted-string")
  511. case size == 1 && r == utf8.RuneError:
  512. return "", fmt.Errorf("mail: invalid utf-8 in quoted-string: %q", p.s)
  513. case escaped:
  514. // quoted-pair = ("\" (VCHAR / WSP))
  515. if !isVchar(r) && !isWSP(r) {
  516. return "", fmt.Errorf("mail: bad character in quoted-string: %q", r)
  517. }
  518. qsb = append(qsb, r)
  519. escaped = false
  520. case isQtext(r) || isWSP(r):
  521. // qtext (printable US-ASCII excluding " and \), or
  522. // FWS (almost; we're ignoring CRLF)
  523. qsb = append(qsb, r)
  524. case r == '"':
  525. break Loop
  526. case r == '\\':
  527. escaped = true
  528. default:
  529. return "", fmt.Errorf("mail: bad character in quoted-string: %q", r)
  530. }
  531. i += size
  532. }
  533. p.s = p.s[i+1:]
  534. return string(qsb), nil
  535. }
  536. // consumeAtom parses an RFC 5322 atom at the start of p.
  537. // If dot is true, consumeAtom parses an RFC 5322 dot-atom instead.
  538. // If permissive is true, consumeAtom will not fail on:
  539. // - leading/trailing/double dots in the atom (see golang.org/issue/4938)
  540. // - special characters (RFC 5322 3.2.3) except '<', '>', ':' and '"' (see golang.org/issue/21018)
  541. func (p *addrParser) consumeAtom(dot bool, permissive bool) (atom string, err error) {
  542. i := 0
  543. Loop:
  544. for {
  545. r, size := utf8.DecodeRuneInString(p.s[i:])
  546. switch {
  547. case size == 1 && r == utf8.RuneError:
  548. return "", fmt.Errorf("mail: invalid utf-8 in address: %q", p.s)
  549. case size == 0 || !isAtext(r, dot, permissive):
  550. break Loop
  551. default:
  552. i += size
  553. }
  554. }
  555. if i == 0 {
  556. return "", errors.New("mail: invalid string")
  557. }
  558. atom, p.s = p.s[:i], p.s[i:]
  559. if !permissive {
  560. if strings.HasPrefix(atom, ".") {
  561. return "", errors.New("mail: leading dot in atom")
  562. }
  563. if strings.Contains(atom, "..") {
  564. return "", errors.New("mail: double dot in atom")
  565. }
  566. if strings.HasSuffix(atom, ".") {
  567. return "", errors.New("mail: trailing dot in atom")
  568. }
  569. }
  570. return atom, nil
  571. }
  572. func (p *addrParser) consumeDisplayNameComment() (string, error) {
  573. if !p.consume('(') {
  574. return "", errors.New("mail: comment does not start with (")
  575. }
  576. comment, ok := p.consumeComment()
  577. if !ok {
  578. return "", errors.New("mail: misformatted parenthetical comment")
  579. }
  580. // TODO(stapelberg): parse quoted-string within comment
  581. words := strings.FieldsFunc(comment, func(r rune) bool { return r == ' ' || r == '\t' })
  582. for idx, word := range words {
  583. decoded, isEncoded, err := p.decodeRFC2047Word(word)
  584. if err != nil {
  585. return "", err
  586. }
  587. if isEncoded {
  588. words[idx] = decoded
  589. }
  590. }
  591. return strings.Join(words, " "), nil
  592. }
  593. func (p *addrParser) consume(c byte) bool {
  594. if p.empty() || p.peek() != c {
  595. return false
  596. }
  597. p.s = p.s[1:]
  598. return true
  599. }
  600. // skipSpace skips the leading space and tab characters.
  601. func (p *addrParser) skipSpace() {
  602. p.s = strings.TrimLeft(p.s, " \t")
  603. }
  604. func (p *addrParser) peek() byte {
  605. return p.s[0]
  606. }
  607. func (p *addrParser) empty() bool {
  608. return p.len() == 0
  609. }
  610. func (p *addrParser) len() int {
  611. return len(p.s)
  612. }
  613. // skipCFWS skips CFWS as defined in RFC5322.
  614. func (p *addrParser) skipCFWS() bool {
  615. p.skipSpace()
  616. for {
  617. if !p.consume('(') {
  618. break
  619. }
  620. if _, ok := p.consumeComment(); !ok {
  621. return false
  622. }
  623. p.skipSpace()
  624. }
  625. return true
  626. }
  627. func (p *addrParser) consumeComment() (string, bool) {
  628. // '(' already consumed.
  629. depth := 1
  630. var comment string
  631. for {
  632. if p.empty() || depth == 0 {
  633. break
  634. }
  635. if p.peek() == '\\' && p.len() > 1 {
  636. p.s = p.s[1:]
  637. } else if p.peek() == '(' {
  638. depth++
  639. } else if p.peek() == ')' {
  640. depth--
  641. }
  642. if depth > 0 {
  643. comment += p.s[:1]
  644. }
  645. p.s = p.s[1:]
  646. }
  647. return comment, depth == 0
  648. }
  649. func (p *addrParser) decodeRFC2047Word(s string) (word string, isEncoded bool, err error) {
  650. if p.dec != nil {
  651. word, err = p.dec.Decode(s)
  652. } else {
  653. word, err = rfc2047Decoder.Decode(s)
  654. }
  655. if err == nil {
  656. return word, true, nil
  657. }
  658. if _, ok := err.(charsetError); ok {
  659. return s, true, err
  660. }
  661. // Ignore invalid RFC 2047 encoded-word errors.
  662. return s, false, nil
  663. }
  664. var rfc2047Decoder = mime.WordDecoder{
  665. CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
  666. return nil, charsetError(charset)
  667. },
  668. }
  669. type charsetError string
  670. func (e charsetError) Error() string {
  671. return fmt.Sprintf("charset not supported: %q", string(e))
  672. }
  673. // isAtext reports whether r is an RFC 5322 atext character.
  674. // If dot is true, period is included.
  675. // If permissive is true, RFC 5322 3.2.3 specials is included,
  676. // except '<', '>', ':' and '"'.
  677. func isAtext(r rune, dot, permissive bool) bool {
  678. switch r {
  679. case '.':
  680. return dot
  681. // RFC 5322 3.2.3. specials
  682. case '(', ')', '[', ']', ';', '@', '\\', ',':
  683. return permissive
  684. case '<', '>', '"', ':':
  685. return false
  686. }
  687. return isVchar(r)
  688. }
  689. // isQtext reports whether r is an RFC 5322 qtext character.
  690. func isQtext(r rune) bool {
  691. // Printable US-ASCII, excluding backslash or quote.
  692. if r == '\\' || r == '"' {
  693. return false
  694. }
  695. return isVchar(r)
  696. }
  697. // quoteString renders a string as an RFC 5322 quoted-string.
  698. func quoteString(s string) string {
  699. var buf strings.Builder
  700. buf.WriteByte('"')
  701. for _, r := range s {
  702. if isQtext(r) || isWSP(r) {
  703. buf.WriteRune(r)
  704. } else if isVchar(r) {
  705. buf.WriteByte('\\')
  706. buf.WriteRune(r)
  707. }
  708. }
  709. buf.WriteByte('"')
  710. return buf.String()
  711. }
  712. // isVchar reports whether r is an RFC 5322 VCHAR character.
  713. func isVchar(r rune) bool {
  714. // Visible (printing) characters.
  715. return '!' <= r && r <= '~' || isMultibyte(r)
  716. }
  717. // isMultibyte reports whether r is a multi-byte UTF-8 character
  718. // as supported by RFC 6532
  719. func isMultibyte(r rune) bool {
  720. return r >= utf8.RuneSelf
  721. }
  722. // isWSP reports whether r is a WSP (white space).
  723. // WSP is a space or horizontal tab (RFC 5234 Appendix B).
  724. func isWSP(r rune) bool {
  725. return r == ' ' || r == '\t'
  726. }