summary refs log tree commit diff stats
path: root/src/common/ctcp.c
blob: 373923725dc1b0347c747840f5afcd4e152a91ac (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/* X-Chat
 * Copyright (C) 1998 Peter Zelezny.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "xchat.h"
#include "cfgfiles.h"
#include "util.h"
#include "modes.h"
#include "outbound.h"
#include "ignore.h"
#include "inbound.h"
#include "dcc.h"
#include "text.h"
#include "ctcp.h"
#include "server.h"
#include "xchatc.h"


static void
ctcp_reply (session *sess, char *nick, char *word[], char *word_eol[],
				char *conf)
{
	char tbuf[4096];	/* can receive 2048 from IRC, so this is enough */

	conf = strdup (conf);
	/* process %C %B etc */
	check_special_chars (conf, TRUE);
	auto_insert (tbuf, sizeof (tbuf), conf, word, word_eol, "", "", word_eol[5],
					 server_get_network (sess->server, TRUE), "", "", nick);
	free (conf);
	handle_command (sess, tbuf, FALSE);
}

static int
ctcp_check (session *sess, char *nick, char *word[], char *word_eol[],
				char *ctcp)
{
	int ret = 0;
	char *po;
	struct popup *pop;
	GSList *list = ctcp_list;

	po = strchr (ctcp, '\001');
	if (po)
		*po = 0;

	po = strchr (word_eol[5], '\001');
	if (po)
		*po = 0;

	while (list)
	{
		pop = (struct popup *) list->data;
		if (!strcasecmp (ctcp, pop->name))
		{
			ctcp_reply (sess, nick, word, word_eol, pop->cmd);
			ret = 1;
		}
		list = list->next;
	}
	return ret;
}

void
ctcp_handle (session *sess, char *to, char *nick, char *ip,
				 char *msg, char *word[], char *word_eol[], int id)
{
	char *po;
	session *chansess;
	server *serv = sess->server;
	char outbuf[1024];
	int ctcp_offset = 2;

	if (serv->have_idmsg && (word[4][1] == '+' || word[4][1] == '-') )
			ctcp_offset = 3;

	/* consider DCC to be different from other CTCPs */
	if (!strncasecmp (msg, "DCC", 3))
	{
		/* but still let CTCP replies override it */
		if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
		{
			if (!ignore_check (word[1], IG_DCC))
				handle_dcc (sess, nick, word, word_eol);
		}
		return;
	}

	/* consider ACTION to be different from other CTCPs. Check
      ignore as if it was a PRIV/CHAN. */
	if (!strncasecmp (msg, "ACTION ", 7))
	{
		if (is_channel (serv, to))
		{
			/* treat a channel action as a CHAN */
			if (ignore_check (word[1], IG_CHAN))
				return;
		} else
		{
			/* treat a private action as a PRIV */
			if (ignore_check (word[1], IG_PRIV))
				return;
		}

		/* but still let CTCP replies override it */
		if (ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
			goto generic;

		inbound_action (sess, to, nick, ip, msg + 7, FALSE, id);
		return;
	}

	if (ignore_check (word[1], IG_CTCP))
		return;

	if (!strcasecmp (msg, "VERSION") && !prefs.hidever)
	{
		snprintf (outbuf, sizeof (outbuf), "VERSION XChat-WDK "PACKAGE_VERSION" [x%d] / %s",
					 get_cpu_arch (), get_cpu_str ());
		serv->p_nctcp (serv, nick, outbuf);
	}

	if (!ctcp_check (sess, nick, word, word_eol, word[4] + ctcp_offset))
	{
		if (!strncasecmp (msg, "SOUND", 5))
		{
			po = strchr (word[5], '\001');
			if (po)
				po[0] = 0;

			if (is_channel (sess->server, to))
			{
				chansess = find_channel (sess->server, to);
				if (!chansess)
					chansess = sess;

				EMIT_SIGNAL (XP_TE_CTCPSNDC, chansess, word[5],
								 nick, to, NULL, 0);
			} else
			{
				EMIT_SIGNAL (XP_TE_CTCPSND, sess->server->front_session, word[5],
								 nick, NULL, NULL, 0);
			}

			/* don't let IRCers specify path */
#ifdef WIN32
			if (strchr (word[5], '/') == NULL && strchr (word[5], '\\') == NULL)
#else
			if (strchr (word[5], '/') == NULL)
#endif
				sound_play (word[5], TRUE);
			return;
		}
	}

generic:
	po = strchr (msg, '\001');
	if (po)
		po[0] = 0;

	if (!is_channel (sess->server, to))
	{
		EMIT_SIGNAL (XP_TE_CTCPGEN, sess->server->front_session, msg, nick,
						 NULL, NULL, 0);
	} else
	{
		chansess = find_channel (sess->server, to);
		if (!chansess)
			chansess = sess;
		EMIT_SIGNAL (XP_TE_CTCPGENC, chansess, msg, nick, to, NULL, 0);
	}
}
an> if( exists $scripts{$package} ) { my $pkg_info = pkg_info( $package ); my $filename = File::Basename::basename( $pkg_info->{filename} ); Xchat::printf( qq{'%s' already loaded from '%s'.\n}, $filename, $pkg_info->{filename} ); Xchat::print( 'If this is a different script then it rename and try '. 'loading it again.' ); return 2; } if( open my $source_handle, $file ) { my $source = do {local $/; <$source_handle>}; close $source_handle; # we shouldn't care about things after __END__ $source =~ s/^__END__.*//ms; # this must come before the eval or the filename will not be found in # Xchat::register $scripts{$package}{filename} = $file; $scripts{$package}{loaded_at} = Time::HiRes::time(); # this must be done before the error check so the unload will remove # any inner packages defined by the script. if a script fails to load # then any inner packages need to be removed as well. my @inner_packages = $source =~ m/^\s*package \s+ ((?:[^\W:]+(?:::)?)+)\s*? # package name # strict version number (?:\d+(?:[.]\d+) # positive integer or decimal-fraction |v\d+(?:[.]\d+){2,})? # dotted-decimal v-string [{;] /mgx; # check if any inner package defined in the to be loaded script has # already been defined by another script my @conflicts; for my $inner ( @inner_packages ) { if( exists $owner_package{ $inner } ) { push @conflicts, $inner; } } # report conflicts and bail out if( @conflicts ) { my $error_message = "'$file' won't be loaded due to conflicting inner packages:\n"; for my $conflict_package ( @conflicts ) { $error_message .= " $conflict_package already defined in " . pkg_info($owner_package{ $conflict_package })->{filename}."\n"; } Xchat::print( $error_message ); return 2; } my $full_path = File::Spec->rel2abs( $file ); $source =~ s/^/#line 1 "$full_path"\n\x7Bpackage $package;/; # make sure we add the closing } even if the last line is a comment if( $source =~ /^#.*\Z/m ) { $source =~ s/^(?=#.*\Z)/\x7D/m; } else { $source =~ s/\Z/\x7D/; } $scripts{$package}{inner_packages} = [ @inner_packages ]; @owner_package{ @inner_packages } = ($package) x @inner_packages; _do_eval( $source ); unless( exists $scripts{$package}{gui_entry} ) { $scripts{$package}{gui_entry} = Xchat::Internal::register( "", "unknown", "", $file ); } if( $@ ) { # something went wrong $@ =~ s/\(eval \d+\)/$file/g; Xchat::print( "Error loading '$file':\n$@\n" ); # make sure the script list doesn't contain false information unload( $scripts{$package}{filename} ); return 1; } } else { Xchat::print( "Error opening '$file': $!\n" ); return 2; } return 0; } sub _do_eval { no strict; no warnings; eval $_[0]; } sub unload { my $file = shift @_; my $package = file2pkg( $file ); my $pkg_info = pkg_info( $package ); if( $pkg_info ) { # take care of the shutdown callback if( exists $pkg_info->{shutdown} ) { # allow incorrectly written scripts to be unloaded eval { if( ref $pkg_info->{shutdown} eq 'CODE' ) { $pkg_info->{shutdown}->(); } elsif ( $pkg_info->{shutdown} ) { no strict 'refs'; &{$pkg_info->{shutdown}}; } }; } if( exists $pkg_info->{hooks} ) { for my $hook ( @{$pkg_info->{hooks}} ) { Xchat::unhook( $hook, $package ); } } if( exists $pkg_info->{gui_entry} ) { plugingui_remove( $pkg_info->{gui_entry} ); } delete @owner_package{ @{$pkg_info->{inner_packages}} }; for my $inner_package ( @{$pkg_info->{inner_packages}} ) { Symbol::delete_package( $inner_package ); } Symbol::delete_package( $package ); delete $scripts{$package}; return Xchat::EAT_ALL; } else { Xchat::print( qq{"$file" is not loaded.\n} ); return Xchat::EAT_NONE; } } sub unload_all { for my $package ( keys %scripts ) { unload( $scripts{$package}->{filename} ); } return Xchat::EAT_ALL; } sub reload { my $file = shift @_; my $package = file2pkg( $file ); my $pkg_info = pkg_info( $package ); my $fullpath = $file; if( $pkg_info ) { $fullpath = $pkg_info->{filename}; unload( $file ); } load( $fullpath ); return Xchat::EAT_ALL; } sub reload_all { my @dirs = Xchat::get_info( "hexchatdirfs" ) || Xchat::get_info( "hexchatdir" ); push @dirs, File::Spec->catdir( $dirs[0], "plugins" ); for my $dir ( @dirs ) { my $auto_load_glob = File::Spec->catfile( $dir, "*.pl" ); my @scripts = map { $_->{filename} } sort { $a->{loaded_at} <=> $b->{loaded_at} } values %scripts; push @scripts, File::Glob::bsd_glob( $auto_load_glob ); my %seen; @scripts = grep { !$seen{ $_ }++ } @scripts; unload_all(); for my $script ( @scripts ) { if( !pkg_info( file2pkg( $script ) ) ) { load( $script ); } } } } sub expand_homedir { my $file = shift @_; if ( $^O eq "MSWin32" ) { $file =~ s/^~/$ENV{USERPROFILE}/; } else { $file =~ s{^~}{ (getpwuid($>))[7] || $ENV{HOME} || $ENV{LOGDIR} }ex; } return $file; } sub file2pkg { my $string = File::Basename::basename( shift @_ ); $string =~ s/\.pl$//i; $string =~ s|([^A-Za-z0-9/])|'_'.unpack("H*",$1)|eg; return "Xchat::Script::" . $string; } sub pkg_info { my $package = shift @_; return $scripts{$package}; } sub find_external_pkg { my $level = 1; while( my @frame = caller( $level ) ) { return @frame if $frame[0] !~ /(?:^IRC$|^Xchat)/; $level++; } return; } sub find_pkg { my $level = 1; while( my ($package, $file, $line) = caller( $level ) ) { return $package if $package =~ /^Xchat::Script::/; $level++; } my $current_package = get_current_package(); if( defined $current_package ) { return $current_package; } my @frame = find_external_pkg(); my $location; if( $frame[0] or $frame[1] ) { my $calling_package = $frame[0]; if( defined( my $owner = $owner_package{ $calling_package } ) ) { return ($owner, $calling_package); } $location = $frame[1] ? $frame[1] : "package $frame[0]"; $location .= " line $frame[2]"; } else { $location = "unknown location"; } die "Unable to determine which script this hook belongs to. at $location\n"; } # convert function names into code references sub fix_callback { my ($package, $calling_package, $callback) = @_; unless( ref $callback ) { unless( $callback =~ /::/ ) { my $prefix = defined $calling_package ? $calling_package : $package; $callback =~ s/^/${prefix}::/; } no strict 'subs'; $callback = \&{$callback}; } return $callback; } sub get_current_package { return $current_package; } sub set_current_package { my $old_package = $current_package; $current_package = shift; return $old_package; } 1