PageRenderTime 28ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/external/tmail/tmail/header.rb

https://github.com/wyhaines/iowa
Ruby | 895 lines | 668 code | 215 blank | 12 comment | 22 complexity | 461fdda0d5f4df2132d5f5df327a1e14 MD5 | raw file
  1. #
  2. # header.rb
  3. #
  4. # Copyright (c) 1998-2004 Minero Aoki
  5. #
  6. # This program is free software.
  7. # You can distribute/modify this program under the terms of
  8. # the GNU Lesser General Public License version 2.1.
  9. #
  10. require 'tmail/encode'
  11. require 'tmail/address'
  12. require 'tmail/parser'
  13. require 'tmail/config'
  14. require 'tmail/textutils'
  15. module TMail
  16. class HeaderField
  17. include TextUtils
  18. class << self
  19. alias newobj new
  20. def new(name, body, conf = DEFAULT_CONFIG)
  21. klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
  22. klass.newobj body, conf
  23. end
  24. def new_from_port(port, name, conf = DEFAULT_CONFIG)
  25. re = Regexp.new('\A(' + Regexp.quote(name) + '):', 'i')
  26. str = nil
  27. port.ropen {|f|
  28. f.each do |line|
  29. if m = re.match(line) then str = m.post_match.strip
  30. elsif str and /\A[\t ]/ =~ line then str << ' ' << line.strip
  31. elsif /\A-*\s*\z/ =~ line then break
  32. elsif str then break
  33. end
  34. end
  35. }
  36. new(name, str, Config.to_config(conf))
  37. end
  38. def internal_new(name, conf)
  39. FNAME_TO_CLASS[name].newobj('', conf, true)
  40. end
  41. end # class << self
  42. def initialize(body, conf, intern = false)
  43. @body = body
  44. @config = conf
  45. @illegal = false
  46. @parsed = false
  47. if intern
  48. @parsed = true
  49. parse_init
  50. end
  51. end
  52. def inspect
  53. "#<#{self.class} #{@body.inspect}>"
  54. end
  55. def illegal?
  56. @illegal
  57. end
  58. def empty?
  59. ensure_parsed
  60. return true if @illegal
  61. isempty?
  62. end
  63. private
  64. def ensure_parsed
  65. return if @parsed
  66. @parsed = true
  67. parse
  68. end
  69. # defabstract parse
  70. # end
  71. def clear_parse_status
  72. @parsed = false
  73. @illegal = false
  74. end
  75. public
  76. def body
  77. ensure_parsed
  78. v = Decoder.new(s = '')
  79. do_accept v
  80. v.terminate
  81. s
  82. end
  83. def body=(str)
  84. @body = str
  85. clear_parse_status
  86. end
  87. include StrategyInterface
  88. def accept(strategy, dummy1 = nil, dummy2 = nil)
  89. ensure_parsed
  90. do_accept strategy
  91. strategy.terminate
  92. end
  93. # abstract do_accept
  94. end
  95. class UnstructuredHeader < HeaderField
  96. def body
  97. ensure_parsed
  98. @body
  99. end
  100. def body=(arg)
  101. ensure_parsed
  102. @body = arg
  103. end
  104. private
  105. def parse_init
  106. end
  107. def parse
  108. @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
  109. end
  110. def isempty?
  111. not @body
  112. end
  113. def do_accept(strategy)
  114. strategy.text @body
  115. end
  116. end
  117. class StructuredHeader < HeaderField
  118. def comments
  119. ensure_parsed
  120. @comments
  121. end
  122. private
  123. def parse
  124. save = nil
  125. begin
  126. parse_init
  127. do_parse
  128. rescue SyntaxError
  129. if not save and mime_encoded? @body
  130. save = @body
  131. @body = Decoder.decode(save)
  132. retry
  133. elsif save
  134. @body = save
  135. end
  136. @illegal = true
  137. raise if @config.strict_parse?
  138. end
  139. end
  140. def parse_init
  141. @comments = []
  142. init
  143. end
  144. def do_parse
  145. obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
  146. set obj if obj
  147. end
  148. end
  149. class DateTimeHeader < StructuredHeader
  150. PARSE_TYPE = :DATETIME
  151. def date
  152. ensure_parsed
  153. @date
  154. end
  155. def date=(arg)
  156. ensure_parsed
  157. @date = arg
  158. end
  159. private
  160. def init
  161. @date = nil
  162. end
  163. def set(t)
  164. @date = t
  165. end
  166. def isempty?
  167. not @date
  168. end
  169. def do_accept(strategy)
  170. strategy.meta time2str(@date)
  171. end
  172. end
  173. class AddressHeader < StructuredHeader
  174. PARSE_TYPE = :MADDRESS
  175. def addrs
  176. ensure_parsed
  177. @addrs
  178. end
  179. private
  180. def init
  181. @addrs = []
  182. end
  183. def set(a)
  184. @addrs = a
  185. end
  186. def isempty?
  187. @addrs.empty?
  188. end
  189. def do_accept(strategy)
  190. first = true
  191. @addrs.each do |a|
  192. if first
  193. first = false
  194. else
  195. strategy.meta ','
  196. strategy.space
  197. end
  198. a.accept strategy
  199. end
  200. @comments.each do |c|
  201. strategy.space
  202. strategy.meta '('
  203. strategy.text c
  204. strategy.meta ')'
  205. end
  206. end
  207. end
  208. class ReturnPathHeader < AddressHeader
  209. PARSE_TYPE = :RETPATH
  210. def addr
  211. addrs()[0]
  212. end
  213. def spec
  214. a = addr() or return nil
  215. a.spec
  216. end
  217. def routes
  218. a = addr() or return nil
  219. a.routes
  220. end
  221. private
  222. def do_accept(strategy)
  223. a = addr()
  224. strategy.meta '<'
  225. unless a.routes.empty?
  226. strategy.meta a.routes.map {|i| '@' + i }.join(',')
  227. strategy.meta ':'
  228. end
  229. spec = a.spec
  230. strategy.meta spec if spec
  231. strategy.meta '>'
  232. end
  233. end
  234. class SingleAddressHeader < AddressHeader
  235. def addr
  236. addrs()[0]
  237. end
  238. private
  239. def do_accept(strategy)
  240. a = addr()
  241. a.accept strategy
  242. @comments.each do |c|
  243. strategy.space
  244. strategy.meta '('
  245. strategy.text c
  246. strategy.meta ')'
  247. end
  248. end
  249. end
  250. class MessageIdHeader < StructuredHeader
  251. def id
  252. ensure_parsed
  253. @id
  254. end
  255. def id=(arg)
  256. ensure_parsed
  257. @id = arg
  258. end
  259. private
  260. def init
  261. @id = nil
  262. end
  263. def isempty?
  264. not @id
  265. end
  266. def do_parse
  267. @id = @body.slice(MESSAGE_ID) or
  268. raise SyntaxError, "wrong Message-ID format: #{@body}"
  269. end
  270. def do_accept(strategy)
  271. strategy.meta @id
  272. end
  273. end
  274. class ReferencesHeader < StructuredHeader
  275. def refs
  276. ensure_parsed
  277. @refs
  278. end
  279. def each_id
  280. self.refs.each do |i|
  281. yield i if MESSAGE_ID =~ i
  282. end
  283. end
  284. def ids
  285. ensure_parsed
  286. @ids
  287. end
  288. def each_phrase
  289. self.refs.each do |i|
  290. yield i unless MESSAGE_ID =~ i
  291. end
  292. end
  293. def phrases
  294. result = []
  295. each_phrase do |i|
  296. result.push i
  297. end
  298. result
  299. end
  300. private
  301. def init
  302. @refs = []
  303. @ids = []
  304. end
  305. def isempty?
  306. @ids.empty?
  307. end
  308. def do_parse
  309. str = @body
  310. while m = MESSAGE_ID.match(str)
  311. pre = m.pre_match.strip
  312. @refs.push pre unless pre.empty?
  313. @refs.push s = m[0]
  314. @ids.push s
  315. str = m.post_match
  316. end
  317. str = str.strip
  318. @refs.push str unless str.empty?
  319. end
  320. def do_accept(strategy)
  321. first = true
  322. @ids.each do |i|
  323. if first
  324. first = false
  325. else
  326. strategy.space
  327. end
  328. strategy.meta i
  329. end
  330. end
  331. end
  332. class ReceivedHeader < StructuredHeader
  333. PARSE_TYPE = :RECEIVED
  334. def from
  335. ensure_parsed
  336. @from
  337. end
  338. def from=(arg)
  339. ensure_parsed
  340. @from = arg
  341. end
  342. def by
  343. ensure_parsed
  344. @by
  345. end
  346. def by=(arg)
  347. ensure_parsed
  348. @by = arg
  349. end
  350. def via
  351. ensure_parsed
  352. @via
  353. end
  354. def via=(arg)
  355. ensure_parsed
  356. @via = arg
  357. end
  358. def with
  359. ensure_parsed
  360. @with
  361. end
  362. def id
  363. ensure_parsed
  364. @id
  365. end
  366. def id=(arg)
  367. ensure_parsed
  368. @id = arg
  369. end
  370. def _for
  371. ensure_parsed
  372. @_for
  373. end
  374. def _for=(arg)
  375. ensure_parsed
  376. @_for = arg
  377. end
  378. def date
  379. ensure_parsed
  380. @date
  381. end
  382. def date=(arg)
  383. ensure_parsed
  384. @date = arg
  385. end
  386. private
  387. def init
  388. @from = @by = @via = @with = @id = @_for = nil
  389. @with = []
  390. @date = nil
  391. end
  392. def set(args)
  393. @from, @by, @via, @with, @id, @_for, @date = *args
  394. end
  395. def isempty?
  396. @with.empty? and not (@from or @by or @via or @id or @_for or @date)
  397. end
  398. def do_accept(strategy)
  399. list = []
  400. list.push 'from ' + @from if @from
  401. list.push 'by ' + @by if @by
  402. list.push 'via ' + @via if @via
  403. @with.each do |i|
  404. list.push 'with ' + i
  405. end
  406. list.push 'id ' + @id if @id
  407. list.push 'for <' + @_for + '>' if @_for
  408. first = true
  409. list.each do |i|
  410. strategy.space unless first
  411. strategy.meta i
  412. first = false
  413. end
  414. if @date
  415. strategy.meta ';'
  416. strategy.space
  417. strategy.meta time2str(@date)
  418. end
  419. end
  420. end
  421. class KeywordsHeader < StructuredHeader
  422. PARSE_TYPE = :KEYWORDS
  423. def keys
  424. ensure_parsed
  425. @keys
  426. end
  427. private
  428. def init
  429. @keys = []
  430. end
  431. def set(a)
  432. @keys = a
  433. end
  434. def isempty?
  435. @keys.empty?
  436. end
  437. def do_accept(strategy)
  438. first = true
  439. @keys.each do |i|
  440. if first
  441. first = false
  442. else
  443. strategy.meta ','
  444. end
  445. strategy.meta i
  446. end
  447. end
  448. end
  449. class EncryptedHeader < StructuredHeader
  450. PARSE_TYPE = :ENCRYPTED
  451. def encrypter
  452. ensure_parsed
  453. @encrypter
  454. end
  455. def encrypter=(arg)
  456. ensure_parsed
  457. @encrypter = arg
  458. end
  459. def keyword
  460. ensure_parsed
  461. @keyword
  462. end
  463. def keyword=(arg)
  464. ensure_parsed
  465. @keyword = arg
  466. end
  467. private
  468. def init
  469. @encrypter = nil
  470. @keyword = nil
  471. end
  472. def set(args)
  473. @encrypter, @keyword = args
  474. end
  475. def isempty?
  476. not (@encrypter or @keyword)
  477. end
  478. def do_accept(strategy)
  479. if @key
  480. strategy.meta @encrypter + ','
  481. strategy.space
  482. strategy.meta @keyword
  483. else
  484. strategy.meta @encrypter
  485. end
  486. end
  487. end
  488. class MimeVersionHeader < StructuredHeader
  489. PARSE_TYPE = :MIMEVERSION
  490. def major
  491. ensure_parsed
  492. @major
  493. end
  494. def major=(arg)
  495. ensure_parsed
  496. @major = arg
  497. end
  498. def minor
  499. ensure_parsed
  500. @minor
  501. end
  502. def minor=(arg)
  503. ensure_parsed
  504. @minor = arg
  505. end
  506. def version
  507. sprintf('%d.%d', major, minor)
  508. end
  509. private
  510. def init
  511. @major = nil
  512. @minor = nil
  513. end
  514. def set(args)
  515. @major, @minor = *args
  516. end
  517. def isempty?
  518. not (@major or @minor)
  519. end
  520. def do_accept(strategy)
  521. strategy.meta sprintf('%d.%d', @major, @minor)
  522. end
  523. end
  524. class ContentTypeHeader < StructuredHeader
  525. PARSE_TYPE = :CTYPE
  526. def main_type
  527. ensure_parsed
  528. @main
  529. end
  530. def main_type=(arg)
  531. ensure_parsed
  532. @main = arg.downcase
  533. end
  534. def sub_type
  535. ensure_parsed
  536. @sub
  537. end
  538. def sub_type=(arg)
  539. ensure_parsed
  540. @sub = arg.downcase
  541. end
  542. def content_type
  543. ensure_parsed
  544. @sub ? sprintf('%s/%s', @main, @sub) : @main
  545. end
  546. def params
  547. ensure_parsed
  548. @params
  549. end
  550. def [](key)
  551. ensure_parsed
  552. @params and @params[key]
  553. end
  554. def []=(key, val)
  555. ensure_parsed
  556. (@params ||= {})[key] = val
  557. end
  558. private
  559. def init
  560. @main = @sub = @params = nil
  561. end
  562. def set(args)
  563. @main, @sub, @params = *args
  564. end
  565. def isempty?
  566. not (@main or @sub)
  567. end
  568. def do_accept(strategy)
  569. if @sub
  570. strategy.meta sprintf('%s/%s', @main, @sub)
  571. else
  572. strategy.meta @main
  573. end
  574. @params.each do |k,v|
  575. strategy.meta ';'
  576. strategy.space
  577. strategy.kv_pair k, v
  578. end
  579. end
  580. end
  581. class ContentTransferEncodingHeader < StructuredHeader
  582. PARSE_TYPE = :CENCODING
  583. def encoding
  584. ensure_parsed
  585. @encoding
  586. end
  587. def encoding=(arg)
  588. ensure_parsed
  589. @encoding = arg
  590. end
  591. private
  592. def init
  593. @encoding = nil
  594. end
  595. def set(s)
  596. @encoding = s
  597. end
  598. def isempty?
  599. not @encoding
  600. end
  601. def do_accept(strategy)
  602. strategy.meta @encoding.capitalize
  603. end
  604. end
  605. class ContentDispositionHeader < StructuredHeader
  606. PARSE_TYPE = :CDISPOSITION
  607. def disposition
  608. ensure_parsed
  609. @disposition
  610. end
  611. def disposition=(str)
  612. ensure_parsed
  613. @disposition = str.downcase
  614. end
  615. def params
  616. ensure_parsed
  617. @params
  618. end
  619. def [](key)
  620. ensure_parsed
  621. @params and @params[key]
  622. end
  623. def []=(key, val)
  624. ensure_parsed
  625. (@params ||= {})[key] = val
  626. end
  627. private
  628. def init
  629. @disposition = @params = nil
  630. end
  631. def set(args)
  632. @disposition, @params = *args
  633. end
  634. def isempty?
  635. not @disposition and (not @params or @params.empty?)
  636. end
  637. def do_accept(strategy)
  638. strategy.meta @disposition
  639. @params.each do |k,v|
  640. strategy.meta ';'
  641. strategy.space
  642. strategy.kv_pair k, v
  643. end
  644. end
  645. end
  646. class HeaderField # redefine
  647. FNAME_TO_CLASS = {
  648. 'date' => DateTimeHeader,
  649. 'resent-date' => DateTimeHeader,
  650. 'to' => AddressHeader,
  651. 'cc' => AddressHeader,
  652. 'bcc' => AddressHeader,
  653. 'from' => AddressHeader,
  654. 'reply-to' => AddressHeader,
  655. 'resent-to' => AddressHeader,
  656. 'resent-cc' => AddressHeader,
  657. 'resent-bcc' => AddressHeader,
  658. 'resent-from' => AddressHeader,
  659. 'resent-reply-to' => AddressHeader,
  660. 'sender' => SingleAddressHeader,
  661. 'resent-sender' => SingleAddressHeader,
  662. 'return-path' => ReturnPathHeader,
  663. 'message-id' => MessageIdHeader,
  664. 'resent-message-id' => MessageIdHeader,
  665. 'in-reply-to' => ReferencesHeader,
  666. 'received' => ReceivedHeader,
  667. 'references' => ReferencesHeader,
  668. 'keywords' => KeywordsHeader,
  669. 'encrypted' => EncryptedHeader,
  670. 'mime-version' => MimeVersionHeader,
  671. 'content-type' => ContentTypeHeader,
  672. 'content-transfer-encoding' => ContentTransferEncodingHeader,
  673. 'content-disposition' => ContentDispositionHeader,
  674. 'content-id' => MessageIdHeader,
  675. 'subject' => UnstructuredHeader,
  676. 'comments' => UnstructuredHeader,
  677. 'content-description' => UnstructuredHeader
  678. }
  679. end
  680. end # module TMail