27
26
from email.mime.base import MIMEBase
28
27
from email.mime.multipart import MIMEMultipart
29
from email.mime.nonmultipart import MIMENonMultipart
30
28
from email.mime.text import MIMEText
32
30
from cloudinit import handlers
50
48
UNDEF_TYPE = "text/plain"
51
49
ARCHIVE_UNDEF_TYPE = "text/cloud-config"
53
# This seems to hit most of the gzip possible content types.
56
'application/gzip-compressed',
57
'application/gzipped',
58
'application/x-compress',
59
'application/x-compressed',
60
'application/x-gunzip',
62
'application/x-gzip-compressed',
65
51
# Msg header used to track attachments
66
52
ATTACHMENT_FIELD = 'Number-Attachments'
70
56
EXAMINE_FOR_LAUNCH_INDEX = ["text/cloud-config"]
73
def _replace_header(msg, key, value):
78
def _set_filename(msg, filename):
79
del msg['Content-Disposition']
80
msg.add_header('Content-Disposition',
81
'attachment', filename=str(filename))
84
59
class UserDataProcessor(object):
85
60
def __init__(self, paths):
104
75
ctype_orig = part.get_content_type()
105
76
payload = part.get_payload(decode=True)
106
was_compressed = False
108
# When the message states it is of a gzipped content type ensure
109
# that we attempt to decode said payload so that the decompressed
110
# data can be examined (instead of the compressed data).
111
if ctype_orig in DECOMP_TYPES:
113
payload = util.decomp_gzip(payload, quiet=False)
114
# At this point we don't know what the content-type is
115
# since we just decompressed it.
117
was_compressed = True
118
except util.DecompressionError as e:
119
LOG.warn("Failed decompressing payload from %s of length"
120
" %s due to: %s", ctype_orig, len(payload), e)
123
# Attempt to figure out the payloads content-type
124
78
if not ctype_orig:
125
79
ctype_orig = UNDEF_TYPE
126
81
if ctype_orig in TYPE_NEEDED:
127
ctype = find_ctype(payload)
82
ctype = handlers.type_from_starts_with(payload)
129
85
ctype = ctype_orig
131
# In the case where the data was compressed, we want to make sure
132
# that we create a new message that contains the found content
133
# type with the uncompressed content since later traversals of the
134
# messages will expect a part not compressed.
136
maintype, subtype = ctype.split("/", 1)
137
n_part = MIMENonMultipart(maintype, subtype)
138
n_part.set_payload(payload)
139
# Copy various headers from the old part to the new one,
140
# but don't include all the headers since some are not useful
141
# after decoding and decompression.
142
if part.get_filename():
143
_set_filename(n_part, part.get_filename())
144
for h in ('Launch-Index',):
146
_replace_header(n_part, h, str(part[h]))
149
87
if ctype != ctype_orig:
150
_replace_header(part, CONTENT_TYPE, ctype)
88
if CONTENT_TYPE in part:
89
part.replace_header(CONTENT_TYPE, ctype)
91
part[CONTENT_TYPE] = ctype
152
93
if ctype in INCLUDE_TYPES:
153
94
self._do_include(payload, append_msg)
157
98
self._explode_archive(payload, append_msg)
160
# TODO(harlowja): Should this be happening, shouldn't
101
# Should this be happening, shouldn't
161
102
# the part header be modified and not the base?
162
_replace_header(base_msg, CONTENT_TYPE, ctype)
103
if CONTENT_TYPE in base_msg:
104
base_msg.replace_header(CONTENT_TYPE, ctype)
106
base_msg[CONTENT_TYPE] = ctype
164
108
self._attach_part(append_msg, part)
195
139
def _process_before_attach(self, msg, attached_id):
196
140
if not msg.get_filename():
197
_set_filename(msg, PART_FN_TPL % (attached_id))
141
msg.add_header('Content-Disposition',
142
'attachment', filename=PART_FN_TPL % (attached_id))
198
143
self._attach_launch_index(msg)
200
145
def _do_include(self, content, append_msg):
272
217
msg.set_payload(content)
274
219
if 'filename' in ent:
275
_set_filename(msg, ent['filename'])
220
msg.add_header('Content-Disposition',
221
'attachment', filename=ent['filename'])
276
222
if 'launch-index' in ent:
277
223
msg.add_header('Launch-Index', str(ent['launch-index']))
279
225
for header in list(ent.keys()):
280
if header.lower() in ('content', 'filename', 'type',
281
'launch-index', 'content-disposition',
282
ATTACHMENT_FIELD.lower(),
283
CONTENT_TYPE.lower()):
226
if header in ('content', 'filename', 'type', 'launch-index'):
285
228
msg.add_header(header, ent[header])
295
238
outer_msg[ATTACHMENT_FIELD] = '0'
297
240
if new_count is not None:
298
_replace_header(outer_msg, ATTACHMENT_FIELD, str(new_count))
241
outer_msg.replace_header(ATTACHMENT_FIELD, str(new_count))
300
243
fetched_count = 0
302
245
fetched_count = int(outer_msg.get(ATTACHMENT_FIELD))
303
246
except (ValueError, TypeError):
304
_replace_header(outer_msg, ATTACHMENT_FIELD, str(fetched_count))
247
outer_msg.replace_header(ATTACHMENT_FIELD, str(fetched_count))
305
248
return fetched_count
307
250
def _attach_part(self, outer_msg, part):
333
276
if "mime-version:" in data[0:4096].lower():
334
277
msg = email.message_from_string(data)
335
278
for (key, val) in headers.iteritems():
336
_replace_header(msg, key, val)
280
msg.replace_header(key, val)
338
284
mtype = headers.get(CONTENT_TYPE, NOT_MULTIPART_TYPE)
339
285
maintype, subtype = mtype.split("/", 1)