icfs 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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