icfs 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/bin/icfs_demo_fcgi.rb +2 -0
  3. data/{bin/icfs_demo_ssl_gen.rb → devel/demo/ssl_gen.rb} +25 -13
  4. data/devel/demo/ssl_gen.yml +14 -0
  5. data/devel/icfs-wrk/Dockerfile +1 -1
  6. data/devel/run/base.rb +92 -0
  7. data/devel/run/copy-s3.rb +2 -0
  8. data/devel/run/email.rb +36 -0
  9. data/devel/run/email_imap.rb +43 -0
  10. data/devel/run/email_smime.rb +47 -0
  11. data/devel/run/init-icfs.rb +2 -0
  12. data/devel/run/webrick.rb +5 -57
  13. data/lib/icfs/api.rb +101 -90
  14. data/lib/icfs/cache.rb +2 -0
  15. data/lib/icfs/cache_elastic.rb +127 -125
  16. data/lib/icfs/{web/config.rb → config.rb} +3 -3
  17. data/lib/icfs/{web/config_redis.rb → config_redis.rb} +8 -8
  18. data/lib/icfs/{web/config_s3.rb → config_s3.rb} +8 -8
  19. data/lib/icfs/demo/auth.rb +5 -7
  20. data/lib/icfs/demo/static.rb +2 -0
  21. data/lib/icfs/elastic.rb +10 -8
  22. data/lib/icfs/email/basic.rb +242 -0
  23. data/lib/icfs/email/core.rb +293 -0
  24. data/lib/icfs/email/from.rb +52 -0
  25. data/lib/icfs/email/imap.rb +148 -0
  26. data/lib/icfs/email/smime.rb +139 -0
  27. data/lib/icfs/items.rb +5 -3
  28. data/lib/icfs/store.rb +20 -18
  29. data/lib/icfs/store_fs.rb +7 -5
  30. data/lib/icfs/store_s3.rb +4 -2
  31. data/lib/icfs/users.rb +5 -3
  32. data/lib/icfs/users_fs.rb +8 -6
  33. data/lib/icfs/users_redis.rb +12 -10
  34. data/lib/icfs/users_s3.rb +6 -4
  35. data/lib/icfs/utils/backup.rb +30 -29
  36. data/lib/icfs/utils/check.rb +36 -34
  37. data/lib/icfs/validate.rb +24 -15
  38. data/lib/icfs/web/auth_ssl.rb +7 -9
  39. data/lib/icfs/web/client.rb +671 -679
  40. data/lib/icfs.rb +174 -10
  41. metadata +16 -7
  42. data/devel/devel-webrick.yml +0 -49
@@ -9,10 +9,11 @@
9
9
  # This program is distributed WITHOUT ANY WARRANTY; without even the
10
10
  # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
11
 
12
+ # frozen_string_literal: true
13
+
12
14
  require_relative 'config'
13
15
 
14
16
  module ICFS
15
- module Web
16
17
 
17
18
  ##########################################################################
18
19
  # Configuration storage implemented in S3
@@ -32,7 +33,7 @@ class ConfigS3 < Config
32
33
  super(defaults)
33
34
  @s3 = s3
34
35
  @bck = bucket
35
- @pre = prefix || ''.freeze
36
+ @pre = prefix || ''
36
37
  end
37
38
 
38
39
 
@@ -40,10 +41,10 @@ class ConfigS3 < Config
40
41
  # (see Config#load)
41
42
  #
42
43
  def load(unam)
43
- Items.validate(unam, 'User/Role/Group name'.freeze, Items::FieldUsergrp)
44
+ Items.validate(unam, 'User/Role/Group name', Items::FieldUsergrp)
44
45
  @unam = unam.dup
45
46
  json = @s3.get_object( bucket: @bck, key: _key(unam) ).body.read
46
- @data = Items.parse(json, 'Config values'.freeze, Config::ValConfig)
47
+ @data = Items.parse(json, 'Config values', Config::ValConfig)
47
48
  return true
48
49
  rescue
49
50
  @data = {}
@@ -55,12 +56,11 @@ class ConfigS3 < Config
55
56
  # (see Config#save)
56
57
  #
57
58
  def save()
58
- raise(RuntimeError, 'Save requires a user name'.freeze) if !@unam
59
- json = Items.generate(@data, 'Config values'.freeze, Config::ValConfig)
59
+ raise(RuntimeError, 'Save requires a user name') if !@unam
60
+ json = Items.generate(@data, 'Config values', Config::ValConfig)
60
61
  @s3.put_object( bucket: @bck, key: _key(@unam), body: json )
61
62
  end # def save()
62
63
 
63
- end # class ICFS::Web::ConfigS3
64
+ end # class ICFS::ConfigS3
64
65
 
65
- end # module ICFS::Web
66
66
  end # module ICFS
@@ -8,6 +8,8 @@
8
8
  # This program is distributed WITHOUT ANY WARRANTY; without even the
9
9
  # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
10
 
11
+ # frozen_string_literal: true
12
+
11
13
  require 'rack'
12
14
 
13
15
  module ICFS
@@ -27,12 +29,10 @@ class Auth
27
29
  #
28
30
  # @param app [Object] The rack app
29
31
  # @param api [Object] the ICFS API
30
- # @param cfg [Object] the ICFS Web Config object
31
32
  #
32
- def initialize(app, api, cfg)
33
+ def initialize(app, api)
33
34
  @app = app
34
35
  @api = api
35
- @cfg = cfg
36
36
  end
37
37
 
38
38
 
@@ -42,7 +42,7 @@ class Auth
42
42
  def call(env)
43
43
 
44
44
  # login
45
- if env['PATH_INFO'] == '/login'.freeze
45
+ if env['PATH_INFO'] == '/login'
46
46
  user = env['QUERY_STRING']
47
47
  body = 'User set'
48
48
 
@@ -59,14 +59,12 @@ class Auth
59
59
  cookies = Rack::Request.new(env).cookies
60
60
  user = cookies['icfs-user']
61
61
  if !user
62
- return [400, {'Content-Type' => 'text/plain'}, ['Login first'.freeze]]
62
+ return [400, {'Content-Type' => 'text/plain'}, ['Login first']]
63
63
  end
64
64
 
65
65
  # set up for the call
66
66
  @api.user = user
67
67
  env['icfs'] = @api
68
- @cfg.load(user)
69
- env['icfs.config'] = @cfg
70
68
  return @app.call(env)
71
69
 
72
70
  rescue ICFS::Error::NotFound, ICFS::Error::Value => err
@@ -8,6 +8,8 @@
8
8
  # This program is distributed WITHOUT ANY WARRANTY; without even the
9
9
  # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
10
 
11
+ # frozen_string_literal: true
12
+
11
13
  #
12
14
  module ICFS
13
15
 
data/lib/icfs/elastic.rb CHANGED
@@ -9,6 +9,8 @@
9
9
  # This program is distributed WITHOUT ANY WARRANTY; without even the
10
10
  # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
11
 
12
+ # frozen_string_literal: true
13
+
12
14
  #
13
15
  module ICFS
14
16
 
@@ -28,12 +30,12 @@ module Elastic
28
30
  # @return [String] JSON encoded object
29
31
  #
30
32
  def _read(ix, id)
31
- url = '%s/_doc/%s/_source'.freeze % [ @map[ix], CGI.escape(id)]
32
- resp = @es.run_request(:get, url, ''.freeze, {})
33
+ url = '%s/_doc/%s/_source' % [ @map[ix], CGI.escape(id)]
34
+ resp = @es.run_request(:get, url, '', {})
33
35
  if resp.status == 404
34
36
  return nil
35
37
  elsif !resp.success?
36
- raise('Elasticsearch read failed'.freeze)
38
+ raise('Elasticsearch read failed')
37
39
  end
38
40
  return resp.body
39
41
  end # def _read()
@@ -47,11 +49,11 @@ module Elastic
47
49
  # @param item [String] JSON encoded object to write
48
50
  #
49
51
  def _write(ix, id, item)
50
- url = '%s/_doc/%s'.freeze % [ @map[ix], CGI.escape(id)]
51
- head = {'Content-Type'.freeze => 'application/json'.freeze}.freeze
52
+ url = '%s/_doc/%s' % [ @map[ix], CGI.escape(id)]
53
+ head = {'Content-Type' => 'application/json'}.freeze
52
54
  resp = @es.run_request(:put, url, item, head)
53
55
  if !resp.success?
54
- raise('Elasticsearch index failed'.freeze)
56
+ raise('Elasticsearch index failed')
55
57
  end
56
58
  end # def _write()
57
59
 
@@ -64,7 +66,7 @@ module Elastic
64
66
  # @param maps [Hash] symbol to Elasticsearch mapping
65
67
  #
66
68
  def create(maps)
67
- head = {'Content-Type'.freeze => 'application/json'.freeze}.freeze
69
+ head = {'Content-Type' => 'application/json'}.freeze
68
70
  maps.each do |ix, map|
69
71
  url = @map[ix]
70
72
  resp = @es.run_request(:put, url, map, head)
@@ -72,7 +74,7 @@ module Elastic
72
74
  puts 'URL: %s' % url
73
75
  puts map
74
76
  puts resp.body
75
- raise('Elasticsearch index create failed: %s'.freeze % ix.to_s)
77
+ raise('Elasticsearch index create failed: %s' % ix.to_s)
76
78
  end
77
79
  end
78
80
  end # def create()
@@ -0,0 +1,242 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ # frozen_string_literal: true
13
+
14
+ require_relative 'core'
15
+
16
+ module ICFS
17
+ module Email
18
+
19
+ ##########################################################################
20
+ # Basic email processing
21
+ #
22
+ # This looks for ICFS email gateway instructions, and processes
23
+ # attachments.
24
+ #
25
+ class Basic
26
+
27
+ ###############################################
28
+ # Strip regex
29
+ StripRx = /^[^[:graph:]]*([[:graph:]].*[[:graph:]])[^[:graph:]]*$/.freeze
30
+
31
+
32
+ ###############################################
33
+ # Strip spaces from collected lines
34
+ #
35
+ def _strip(collect)
36
+ collect.map{ |lr|
37
+ ma = StripRx.match(lr) # include wierd UNICODE spaces
38
+ ma ? ma[1] : nil
39
+ }.compact
40
+ end # def _strip()
41
+
42
+
43
+ ###############################################
44
+ # Check for a boolean
45
+ #
46
+ def _boolean(str)
47
+ case str.downcase
48
+ when 'true', 'yes'
49
+ return true
50
+ when 'false', 'no'
51
+ return false
52
+ else
53
+ return nil
54
+ end
55
+ end # def _boolean()
56
+
57
+
58
+ ###############################################
59
+ # Fields regex
60
+ FieldRx = /^ICFS ([^:[:blank:]]*)[[:blank:]]*:[[:blank:]]*(.*)[[:blank:]]*$/.freeze
61
+
62
+
63
+ ###############################################
64
+ # Regex for stat
65
+ StatRx = /^([+\-]?\d+(\.\d*)?)[^[:graph:]]+([[:graph:]].*[[:graph:]])$/.freeze
66
+
67
+
68
+ ###############################################
69
+ # Look for instructions in the email and process them
70
+ #
71
+ def receive(env)
72
+
73
+ # we only work on text/plain version of the email
74
+ if env[:msg].multipart?
75
+ txt = env[:msg].text_part
76
+ elsif env[:msg].mime_type == 'text/plain'
77
+ txt = env[:msg]
78
+ end
79
+ return [:continue, nil] if !txt
80
+ lines = txt.decoded.lines
81
+
82
+ # User specified values
83
+ collect = nil
84
+ term = nil
85
+ state = nil
86
+ stat_name = nil
87
+ stat_value = nil
88
+ lines.each do |ln|
89
+ # collecting lines
90
+ if collect
91
+ if ln.start_with?(term)
92
+ case state
93
+
94
+ when :tags
95
+ tags = _strip(collect)
96
+ unless tags.empty?
97
+ env[:tags] ||= []
98
+ env[:tags] = env[:tags] + tags
99
+ end
100
+ collect = nil
101
+
102
+ when :perms
103
+ perms = _strip(collect)
104
+ env[:perms] = perms unless perms.empty?
105
+ collect = nil
106
+
107
+ when :stat
108
+ credit = _strip(collect)
109
+ env[:stats] ||= []
110
+ env[:stats] << {
111
+ 'name' => stat_name,
112
+ 'value' => stat_value,
113
+ 'credit' => credit
114
+ }
115
+ collect = nil
116
+
117
+ when :content
118
+ cont = collect.map{|lr| lr.delete("\r")}.join('')
119
+ env[:content] = cont unless cont.empty?
120
+ collect = nil
121
+
122
+ else
123
+ raise ScriptError
124
+ end
125
+ else
126
+ collect << ln
127
+ next
128
+ end
129
+ end
130
+
131
+ next unless ma = FieldRx.match(ln)
132
+ fn = ma[1].downcase
133
+
134
+ case fn
135
+ when 'case'
136
+ env[:caseid] = ma[2].strip
137
+
138
+ when 'entry'
139
+ enum = ma[2].strip.to_i
140
+ env[:entry] = enum if enum != 0
141
+
142
+ when 'title'
143
+ env[:title] = ma[2].strip
144
+
145
+ when 'time'
146
+ if env[:user]
147
+ env[:api].user = env[:user]
148
+ tm = ICFS.time_parse(ma[2].strip, env[:api].config)
149
+ env[:time] = tm if tm
150
+ end
151
+
152
+ when 'tag'
153
+ env[:tags] ||= []
154
+ env[:tags] << ma[2].strip
155
+
156
+ when 'tags'
157
+ collect = []
158
+ state = :tags
159
+ term = 'ICFS'
160
+
161
+ when 'perms'
162
+ collect = []
163
+ state = :perms
164
+ term = 'ICFS'
165
+
166
+ when 'stat'
167
+ next unless pm = StatRx.match(ma[2].strip)
168
+ stat_name = pm[3]
169
+ stat_value = pm[1].to_f
170
+ collect = []
171
+ state = :stat
172
+ term = 'ICFS'
173
+
174
+ when 'content'
175
+ collect = []
176
+ state = :content
177
+ term = ma[2].strip
178
+ term = 'ICFS' if term.empty?
179
+
180
+ when 'save_files'
181
+ val = _boolean(ma[2].strip)
182
+ env[:save_files] = val unless val.nil?
183
+
184
+ when 'save_original'
185
+ val = _boolean(ma[2].strip)
186
+ env[:save_original] = val unless val.nil?
187
+
188
+ when 'save_email'
189
+ val = _boolean(ma[2].strip)
190
+ env[:save_email] = val unless val.nil?
191
+ end
192
+
193
+ end
194
+
195
+ # time defaults to message date
196
+ unless env[:time]
197
+ env[:time] = env[:msg].date.to_time.to_i
198
+ end
199
+
200
+ # title defaults to subject if okay
201
+ unless env[:title]
202
+ # check the subject time
203
+ title = env[:msg].subject
204
+ err = Validate.check(title, Items::FieldTitle)
205
+ env[:title] = title unless err
206
+ end
207
+
208
+ # save the edited email defaults to yes
209
+ unless env.key?(:save_email)
210
+ env[:save_email] = true
211
+ end
212
+
213
+ # save the raw email defaults to no
214
+ unless env.key?(:save_original)
215
+ env[:save_original] = false
216
+ end
217
+
218
+ # save attachments as files
219
+ unless env.key?(:save_files) && !env[:save_files]
220
+ cnt = 0
221
+ env[:msg].attachments.each do |att|
222
+ type = att.header[:content_disposition].disposition_type
223
+ next if type == 'inline'
224
+ cnt += 1
225
+ name = att.filename
226
+ if !name
227
+ ext = MIME::Types[att.content_type].first.extensions.first
228
+ name = 'unnamed_%d.%s' % [cnt, ext]
229
+ end
230
+ env[:files] << { name: name, content: att.decoded }
231
+ end
232
+ env[:msg] = ::Mail.new(env[:msg].without_attachments!.encoded)
233
+ end
234
+
235
+ return :continue
236
+ end # def receive()
237
+
238
+
239
+ end # class ICFS::Email::RxCore
240
+
241
+ end # module ICFS::Email
242
+ end # module ICFS
@@ -0,0 +1,293 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ # frozen_string_literal: true
13
+
14
+ require 'set'
15
+
16
+ module ICFS
17
+
18
+
19
+ ##########################################################################
20
+ # Email integration with ICFS
21
+ #
22
+ module Email
23
+
24
+
25
+ ##########################################################################
26
+ # Core email processing engine.
27
+ #
28
+ class Core
29
+
30
+
31
+ ###############################################
32
+ # A file to attach
33
+ ValFile = {
34
+ method: :hash,
35
+ required: {
36
+ content: Validate::IsString, # content of the file
37
+ name: Items::FieldFilename, # name of the file
38
+ }.freeze
39
+ }.freeze
40
+
41
+
42
+ ###############################################
43
+ # Results of processing a valid message
44
+ ValReceive = {
45
+ method: :hash,
46
+ required: {
47
+ orig: Validate::IsString, # the raw message
48
+ caseid: Items::FieldCaseid, # case to write
49
+ user: Items::FieldUsergrp, # user as author
50
+ files: { # files to attach
51
+ method: :array,
52
+ check: ValFile
53
+ }.freeze,
54
+ }.freeze,
55
+ optional: {
56
+ entry: Validate::IsIntPos, # the entry number
57
+ time: Validate::IsIntPos, # the time of the entry
58
+ title: Items::FieldTitle, # title of the entry
59
+ content: Items::FieldContent, # content of the entry
60
+ tags: { # tags to apply
61
+ method: :array,
62
+ check: Items::FieldTag
63
+ }.freeze,
64
+ perms: { # perms to apply
65
+ method: :array,
66
+ check: Items::FieldPermAny,
67
+ }.freeze,
68
+ stats: { # stats to apply
69
+ method: :array,
70
+ min: 1,
71
+ check: {
72
+ method: :hash,
73
+ required: {
74
+ "name" => Items::FieldStat,
75
+ "value" => Validate::IsFloat,
76
+ "credit" => {
77
+ method: :array,
78
+ min: 1,
79
+ max: 32,
80
+ check: Items::FieldUsergrp
81
+ }.freeze
82
+ }.freeze
83
+ }.freeze
84
+ }.freeze,
85
+ save_raw: Validate::IsBoolean, # save the raw email as a File
86
+ save_msg: Validate::IsBoolean, # save the processed message w/o attach
87
+ }.freeze,
88
+ others: true,
89
+ }.freeze
90
+
91
+
92
+ ###############################################
93
+ # Default title
94
+ DefaultTitle = 'Email gateway default title'
95
+
96
+
97
+ ###############################################
98
+ # Default content
99
+ DefaultContent = 'Entry generated via email gateway with no content.'
100
+
101
+
102
+ ###############################################
103
+ # Filename for original content
104
+ DefaultOrig = 'email_received.eml'
105
+
106
+
107
+ ###############################################
108
+ # Filename for processed content without attachments
109
+ DefaultEmail = 'email.eml'
110
+
111
+
112
+ ###############################################
113
+ # New instance
114
+ #
115
+ # @param api [ICFS::Api] the ICFS API
116
+ # @param log [Logger] The log
117
+ # @param st [Array] the middleware
118
+ #
119
+ def initialize(api, log, st=nil)
120
+ @api = api
121
+ @log = log
122
+ self.stack_set(st) if st
123
+ end # def initialize()
124
+
125
+
126
+ ###############################################
127
+ # Set the middleware stack
128
+ #
129
+ # Each middleware object must respond to #receive and return one of:
130
+ # * :continue - process more middleware
131
+ # * :success - stops further middleare, and records the entry
132
+ # * :failure - stops further middlware and does not record
133
+ #
134
+ def stack_set(st)
135
+ @stack = st
136
+ end # def stack_set()
137
+
138
+
139
+ ###############################################
140
+ # Process a received email using the middleware stack
141
+ #
142
+ # @param msg [::Mail::Message] the email message
143
+ # @return [Array] results, first field is a Symbol, second field is error or
144
+ # the recorded message
145
+ #
146
+ def receive(msg)
147
+ @log.debug('Email: Processing %s' % msg.message_id)
148
+
149
+ # setup the environment
150
+ env = {
151
+ orig: msg.raw_source.dup, # the original text email
152
+ msg: msg, # the email message being worked on
153
+ files: [], # files to attach to the entry
154
+ api: @api, # the ICFS API
155
+ }
156
+
157
+ # process all middleware
158
+ @stack.each do |mid|
159
+ resp, err = mid.receive(env)
160
+ case resp
161
+ when :continue
162
+ next
163
+ when :stop
164
+ break
165
+ when :failure
166
+ return [:failure, err]
167
+ else
168
+ raise ScriptError
169
+ end
170
+ end
171
+
172
+ # check that all required fields were completed
173
+ err = Validate.check(env, ValReceive)
174
+ if err
175
+ @log.info('Email: Invalid: %s' % err.inspect)
176
+ return [:invalid, err]
177
+ end
178
+
179
+ # API set to active user
180
+ @api.user = env[:user]
181
+
182
+ # if an entry was specified
183
+ if env[:entry] && env[:entry] != 0
184
+ ent = @api.entry_read(env[:caseid], env[:entry])
185
+ ent.delete('icfs')
186
+ ent.delete('log')
187
+ ent.delete('user')
188
+ ent.delete('tags') if ent['tags'][0] == ICFS::TagNone
189
+ else
190
+ ent = {}
191
+ ent['caseid'] = env[:caseid]
192
+ end
193
+
194
+ # build entry
195
+ ent['time'] = env[:time] if env[:time]
196
+ ent['title'] = env[:title] if env[:title]
197
+ ent['title'] ||= DefaultTitle
198
+ ent['content'] = env[:content] if env[:content]
199
+ ent['content'] ||= DefaultContent
200
+ if env[:tags]
201
+ ent['tags'] ||= []
202
+ ent['tags'] = (ent['tags'] + env[:tags]).uniq
203
+ end
204
+ ent['perms'] = env[:perms].uniq if env[:perms]
205
+ ent['stats'] = env[:stats] if env[:stats]
206
+
207
+ # files
208
+ files = env[:files].map do |fd|
209
+ tmp = @api.tempfile
210
+ tmp.write(fd[:content])
211
+ { 'name' => fd[:name], 'temp' => tmp }
212
+ end
213
+ if env[:save_original]
214
+ tmp = @api.tempfile
215
+ tmp.write(env[:orig])
216
+ files << { 'name' => DefaultOrig, 'temp' => tmp }
217
+ end
218
+ if env[:save_email]
219
+ tmp = @api.tempfile
220
+ env[:msg].header.fields.delete_if do |fi|
221
+ !FieldsSet.include?(fi.name.downcase)
222
+ end
223
+ tmp.write(env[:msg].encoded)
224
+ files << { 'name' => DefaultEmail, 'temp' => tmp }
225
+ end
226
+ unless files.empty?
227
+ ent['files'] ||= []
228
+ ent['files'] = ent['files'] + files
229
+ end
230
+
231
+ # try to record it
232
+ @api.record(ent, nil, nil, nil)
233
+
234
+ @log.info('Email: Success: %s %d-%d' %
235
+ [ent['caseid'], ent['entry'], ent['log']])
236
+ return [:success, ent]
237
+
238
+ rescue ICFS::Error::Conflict => ex
239
+ @log.warn('Email: Conflict: %s' % ex.message)
240
+ return [:conflict, ex.message]
241
+ rescue ICFS::Error::NotFound => ex
242
+ @log.warn('Email: Not Found: %s' % ex.message)
243
+ return [:notfound, ex.message]
244
+ rescue ICFS::Error::Perms => ex
245
+ @log.warn('Email: Permissions: %s' % ex.message)
246
+ return [:perms, ex.message]
247
+ rescue ICFS::Error::Value => ex
248
+ @log.warn('Email: Value: %s' % ex.message)
249
+ return [:value, ex.message]
250
+ end # def receive()
251
+
252
+
253
+ ###############################################
254
+ # Basic header fields to copy
255
+ #
256
+ CopyFields = [
257
+ "to",
258
+ "cc",
259
+ "message-id",
260
+ "in-reply-to",
261
+ "references",
262
+ "subject",
263
+ "comments",
264
+ "keywords",
265
+ "date",
266
+ "from",
267
+ "sender",
268
+ "reply-to",
269
+ ].freeze
270
+
271
+
272
+ ###############################################
273
+ # Content related fields
274
+ #
275
+ ContentFields = [
276
+ "content-transfer-encoding",
277
+ "content-description",
278
+ "content-disposition",
279
+ "content-type",
280
+ "content-id",
281
+ "content-location",
282
+ ].freeze
283
+
284
+
285
+ ###############################################
286
+ # Set of header fields to copy set
287
+ FieldsSet = Set.new(CopyFields).merge(ContentFields).freeze
288
+
289
+ end # class ICFS::Email::Core
290
+
291
+
292
+ end # module ICFS::Email
293
+ end # module ICFS