3 use vars qw($VERSION %IRSSI);
8 authors => "Jochen 'c0ffee' Eisinger",
9 contact => "c0ffee\@penguin-breeder.org",
10 name => "SILC2 MIME handler",
11 description => "This script implements MIME handlers for SILC2, according to draft-riikonen-silc-flags-payloads-00",
12 license => "GPL2 or any later",
13 url => "http://www.penguin-breeder.org/silc/",
14 changed => "Wed Aug 29 10:45 CET 2003",
25 use File::Temp qw/ tempfile /;
30 my $magic = new File::MMagic;
35 # Loads all mailcap databases specified in the setting
36 # mime_database. Default is ~/.mailcap and /etc/mailcap in
37 # that order. Function is invoked on startup.
39 # MIME Magic Info is also read...
40 sub read_mime_database {
41 # read mailcap databases rfc1525
42 foreach (split /\s+/, Irssi::settings_get_str("mime_database")) {
43 if (( -f $_ ) and ( -R $_ )) {
44 Irssi::printformat(MSGLEVEL_CRAP, 'load_mailcap', $_)
45 if Irssi::settings_get_bool("mime_verbose");
46 $mcap = new Mail::Cap $_;
49 Irssi::printformat(MSGLEVEL_CRAP, 'load_mailcap_fail', $_)
50 if Irssi::settings_get_bool("mime_verbose");
54 $mfile = Irssi::settings_get_str("mime_magic");
57 Irssi::printformat(MSGLEVEL_CRAP, 'load_mime_magic', $mfile);
58 $magic = File::MMagic::new($mfile);
61 if ( not -d Irssi::settings_get_str("mime_temp_dir")) {
63 Irssi::printformat(MSGLEVEL_CRAP, 'no_temp_dir',
64 Irssi::settings_get_str("mime_temp_dir"));
66 Irssi::settings_set_str("mime_temp_dir", "/tmp");
76 sub privmsg_get_query {
77 my ($server, $target, $own) = @_;
79 $dest = $server->query_find($target);
80 if (not defined $dest &&
81 (Irssi::level2bits(settings_get_str("autocreate_query_level")) &
83 (!$own || Irssi::settings_get_bool("autocreate_own_query"))) {
84 $dest = Irssi::Silc::Server::query_create($server->{tag}, $target, 1);
93 # Removes the null-byte escaping from a data block. Returns the
94 # unescaped data. All data send via mime signals must be escaped.
97 $escaped =~ s/\001\001/\000/g;
98 $escaped =~ s/\001\002/\001/g;
106 # Escapes null-bytes for signal transfer. Used to transfer binary data
107 # in null-terminated strings. Returns the escaped data. All data send
108 # via mime signals must be escaped.
110 my ($unescaped) = @_;
111 $unescaped =~ s/\001/\001\002/g;
112 $unescaped =~ s/\000/\001\001/g;
122 sub background_exec {
123 my ($server, $witem, $signed, $sender, $type, $cmd) = @_;
126 $format = "mime_data_received";
127 } elsif ($signed == 0) {
128 $format = "mime_data_received_signed";
129 } elsif ($signed == 1) {
130 $format = "mime_data_received_unknown";
131 } elsif ($signed == 2) {
132 $format = "mime_data_received_failed";
135 if (not $witem->{type}) {
136 $witem = privmsg_get_query($server, $sender, 0);
139 if ($witem->{type}) {
140 $witem->printformat(MSGLEVEL_CRAP, $format, $sender, $type);
141 $witem->window()->command("EXEC " . Irssi::settings_get_str("mime_exec_param") . " " .
144 Irssi::printformat(MSGLEVEL_CRAP, $format, $sender, $type);
145 Irssi::command("EXEC " . Irssi::settings_get_str("mime_exec_param") . " " .
154 # process_mime_entity(WI_ITEM_REC, $signed, $sender, MIME::Entity $msg)
156 # -1 failure, 0 success
157 sub process_mime_entity {
158 my ($server, $witem, $signed, $sender, $entity) = @_;
160 my ($mimetype, $fh, $tempfile, $parser, $ret, $io, $mcap, $cmd);
162 $mimetype = Mail::Field->new('Content-type', $entity->head->get('Content-Type'));
164 # check whether this is message/partial
165 if ($mimetype->type eq "message/partial") {
167 # without an ID i don't know what stream this is related to
168 if ($mimetype->id eq "") {
169 Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "no ID");
173 # the first packet is treated seperatly
174 if ($mimetype->number == 1) {
176 # the IDs should be unique
177 if (defined $partial{$mimetype->id}) {
178 Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "duplicate ID");
179 $fh = $partial{$mimetype->id}{file};
181 unlink $partial{$mimetype->id}{name};
182 undef $partial{$mimetype->id};
186 # create a new record
187 $partial{$mimetype->id}{received} = 1;
188 ($fh, $partial{$mimetype->id}{name})= tempfile("msg-XXXXXXXX", SUFFIX => ".dat", DIR => Irssi::settings_get_str("mime_temp_dir"));
189 $partial{$mimetype->id}{file} = $fh;
190 $partial{$mimetype->id}{count} = 1;
191 $partial{$mimetype->id}{total} = $mimetype->total;
193 } else { # 2nd and later packets
196 if (not defined $partial{$mimetype->id}) {
197 Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "unknown ID");
201 # the 'total' information can be set in any packet,
202 # however it has to be the same all the time
203 if ($mimetype->total > 0) {
205 if (($partial{$mimetype->id}{total} > 0) &&
206 ($partial{$mimetype->id}{total} != $mimetype->total)) {
207 Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "invalid count");
208 $fh = $partial{$mimetype->id}{file};
210 unlink $partial{$mimetype->id}{name};
211 undef $partial{$mimetype->id};
215 $partial{$mimetype->id}{total} = $mimetype->total;
219 # we expect to receive packets in order
220 if ($mimetype->number != ($partial{$mimetype->id}{count} + 1)) {
221 Irssi::printformat(MSGLEVEL_CRAP, 'message_partial_failure', "invalid sequence number");
222 $fh = $partial{$mimetype->id}{file};
224 unlink $partial{$mimetype->id}{name};
225 undef $partial{$mimetype->id};
229 # update our sequence record and save the packet
230 $partial{$mimetype->id}{count} = $mimetype->number;
234 # and save the packet
235 $fh = $partial{$mimetype->id}{file};
236 if ($io = $entity->bodyhandle->open("r")) {
237 while (defined($_ = $io->getline)) { print $fh $_ }
241 # return if this wasn't the last packet
242 if (($partial{$mimetype->id}{total} == 0) ||
243 ($partial{$mimetype->id}{count} < $partial{$mimetype->id}{total})) {
248 $tempfile = $partial{$mimetype->id}{name};
249 $fh = $partial{$mimetype->id}{file};
251 undef $partial{$mimetype->id};
253 $parser = new MIME::Parser;
254 $parser->output_dir(Irssi::settings_get_str("mime_temp_dir"));
255 $mime = $parser->parse_open($tempfile);
257 $ret = process_mime_entity($server, $witem, $signed, $sender, $mime);
259 $parser->filer->purge;
265 # we could check for */parityfec (RTP packets) rfc2733, 3009
267 # save to temporary file
268 ($fh, $tempfile) = tempfile("msg-XXXXXXXX", SUFFIX => ".dat", DIR => Irssi::settings_get_str("mime_temp_dir"));
269 if ($io = $entity->open("r")) {
270 while (defined($_ = $io->getline)) { print $fh $_; }
276 foreach $mcap (@mcaps) {
278 $cmd = $mcap->viewCmd($mimetype->type, $tempfile);
279 next if not defined $cmd;
281 background_exec($server, $witem, $signed, $sender, $mimetype->type, $cmd);
285 unlink $tempfile if Irssi::settings_get_bool("mime_unlink_tempfiles");
286 return $mimetype->type;
292 # signal handler for incoming MIME type messages. If the encoding or
293 # the content type are missing or not parsable, they default to binary
294 # and application/octet-stream respectivly. If a decoder for the given
295 # transfer encoding is available, the message is decoded. If a handler
296 # for the given content type is available in one of the mailcap databases,
297 # the handler is invoked and the signal is stopped. The mailcap databases
298 # are scanned in order of loading. Temporary files are unlinked if the
299 # setting mime_unlink_tempfiles is true.
302 my ($server, $witem, $blob, $sender, $verified) = @_;
304 $parser = new MIME::Parser;
305 $parser->output_dir(Irssi::settings_get_str("mime_temp_dir"));
306 $mime = $parser->parse_data(unescape($blob));
308 $ret = process_mime_entity($server, $witem, $verified, $sender, $mime);
310 $parser->filer->purge;
313 Irssi::signal_stop();
314 } elsif ($ret == -1) {
317 if (not $witem->{type}) {
318 $witem = privmsg_get_query($server, $sender, 0);
320 $theme = $witem->{theme} || Irssi::current_theme;
321 $format = $theme->get_format("fe-common/silc", "message_data");
322 $format =~ s/\$0/$sender/;
323 $format =~ s/\$1/$ret/;
324 if ($witem->{type}) {
325 $witem->print($theme->format_expand($format));
327 Irssi::print($theme->format_expand($format));
329 Irssi::signal_stop();
336 # Sends a file with a given MIME type and transfer encoding.
338 # MMSG [<-sign>] [<-channel>] <target> <file> [<type> [<encoding>]]
340 # Sends a private data message to other user in the network. The message
341 # will be send as a MIME encoded data message.
343 # If -channel option is provided then this command actually send channel
344 # message to the specified channel. The message IS NOT private message, it
345 # is normal channel message.
347 # If the -sign optin is provided, the message will be additionally
350 # Messages that exceed roughly 64k have to be split up into smaller packets.
351 # This is done automatically.
353 # If no transfer-encoding is given it defaults to binary or 7bit for messages
354 # that have to be split up.
356 # If no content-type is given it is guessed using a MIME magic file.
360 # mime_magic - path to MIME magic file, or internal
362 # mime_default_encoding - encoding to use if none specified
363 # mime_temp_dir - where to store temporary files
367 # /MMSG Foobar smiley.gif image/gif binary
368 # /MMSG -channel silc silc.patch text/x-patch 7bit
369 # /MMSG * boing.mp3 audio/mpeg
371 my ($data, $server, $witem) = @_;
373 if ($server->{chat_type} ne "SILC") {
374 Irssi::printformat(MSGLEVEL_CRAP, 'mmsg_chattype');
378 ($sign, $is_channel, $target, $file, $type, $encoding) =
379 $data =~ /^\s*(?:(-sign)?\s+)? # match the -sign
380 \s*(?:(-channel)?\s+)? # match the -channel
383 (?:\s+(\S+) # mime type
384 (?:\s+(\S+))?)?\s* # encoding
387 Irssi::printformat(MSGLEVEL_CRAP, 'mmsg_parameters'), return
388 if ($target eq "") or ($file eq "");
390 Irssi::printformat(MSGLEVEL_CRAP, 'mmsg_file', $file), return
393 $type = $magic->checktype_filename($file)
394 if not defined $type;
395 $encoding = Irssi::settings_get_str("mime_default_encoding")
396 if not defined $encoding;
398 # does the target exist? we don't test that... especially the
399 # -channel parameter is ignored :/
401 if ($target eq "*") {
403 $is_channel = ($witem->{type} eq "CHANNEL" ? "-channel" : "");
404 $target = $witem->{name};
408 $entity = new MIME::Entity->build(
409 'MIME-Version' => "1.0",
410 Encoding => $encoding,
415 ($fh, $tempfile) = tempfile( DIR => Irssi::settings_get_str("mime_temp_dir"));
419 $is_channel = (lc($is_channel) eq "-channel" ? 1 : 0);
420 $sign = (lc($sign) eq "-sign" ? 1 : 0);
423 $dest = $server->channel_find($target);
425 $dest = privmsg_get_query($server, $target, 1);
429 # 21:27 <@pekka> c0ffee: the core routines will crop the message if it
430 # doesn't fit.. I would use a bit shorter than the MAX_LEN
431 # 21:28 <@pekka> c0ffee: -1024 bytes is sufficient
432 # 21:28 <@pekka> c0ffee: should be sufficient in all possible cases
433 if ((stat($tempfile))[7] < 0xfbff) {
434 $format = ($sign ? "mime_data_send_signed" : "mime_data_send");
436 $dest->printformat(MSGLEVEL_CRAP, $format, $type);
438 Irssi::printformat(MSGLEVEL_CRAP, $format, $type);
442 Irssi::signal_emit("mime-send", $server, \$is_channel,
443 $target, escape($entity->stringify), \$sign);
446 $format = ($sign ? "mime_data_multi_signed" : "mime_data_multi");
447 $chunks = ceil((stat $tempfile)[7] / 0xfb00);
449 $dest->printformat(MSGLEVEL_CRAP, $format, $type, $chunks);
451 Irssi::printformat(MSGLEVEL_CRAP, $format, $type, $chunks);
454 open TFILE, $tempfile;
455 $id = sprintf "id-%06d-%08d\@%s", int(rand(65535)), time(), hostname();;
458 read TFILE, $data, 0xfb00;
461 $entity = new MIME::Entity->build(
462 'MIME-Version' => "1.0",
463 Encoding => "binary",
464 Type => "message/partial; id=\"$id\"; number=$chunks" .
465 (eof(TFILE) ? "; total=$chunks" : ""),
468 Irssi::signal_emit("mime-send", $server, \$is_channel,
469 $target, escape($entity->stringify), \$sign);
471 } while (!eof(TFILE));
479 Irssi::signal_add("mime", "sig_mime");
482 Irssi::command_bind("mmsg", "cmd_mmsg");
485 Irssi::settings_add_str("misc", "mime_database",
486 "$ENV{HOME}/.mailcap /etc/mailcap");
487 Irssi::settings_add_bool("misc", "mime_unlink_tempfiles", 1);
488 Irssi::settings_add_str("misc", "mime_default_encoding", "binary");
489 Irssi::settings_add_bool("misc", "mime_verbose", 0);
490 Irssi::settings_add_str("misc", "mime_temp_dir", "/tmp");
491 Irssi::settings_add_str("misc", "mime_magic", "");
492 Irssi::settings_add_str("misc", "mime_exec_param", "-");
495 Irssi::theme_register(['load_mailcap', 'Loading mailcaps from {hilight $0}',
496 'load_mailcap_fail', 'Couldn\'t find {hilight $0}',
497 'message_partial_failure', 'message/partial: {hilight $0-}',
498 'mmsg_chattype', 'command was not designed for this chat type',
499 'mmsg_parameters', 'not enough parameters given',
500 'mmsg_file', 'File {hilight $0} not found',
501 'load_mime_magic', 'Loading MIME magic types from {hilight $0}',
502 'no_temp_dir', 'Directory {hilight $0} does not exist, defaulting to /tmp',
503 'mime_data_received', '{nick $0} sent "{hilight $1}" data message',
504 'mime_data_received_signed', '{nick $0} sent "{hilight $1}" data message (signature {flag_signed})',
505 'mime_data_received_unknown', '{nick $0} sent "{hilight $1}" data message (signature {flag_unknown})',
506 'mime_data_received_failed', '{nick $0} sent "{hilight $1}" data message (signature {flag_failed})',
507 'mime_data_send', 'sending "{hilight $0}" data message',
508 'mime_data_send_signed', 'sending "{hilight $0}" data message (signature {flag_signed})',
509 'mime_data_multi', 'sending "{hilight $0}" data message ($1 chunks)',
510 'mime_data_multi_signed', 'sending "{hilight $0}" data message ($1 chunks, signature {flag_signed})']);
514 read_mime_database();