[Slashdotjp-dev 409] CVS update: slashjp/Slash/Utility/Environment

Back to archive index

Tatsuki SUGIURA sugi****@users*****
2006年 7月 12日 (水) 20:41:41 JST


Index: slashjp/Slash/Utility/Environment/Environment.pm
diff -u slashjp/Slash/Utility/Environment/Environment.pm:1.10 slashjp/Slash/Utility/Environment/Environment.pm:1.11
--- slashjp/Slash/Utility/Environment/Environment.pm:1.10	Mon Oct 17 19:35:15 2005
+++ slashjp/Slash/Utility/Environment/Environment.pm	Wed Jul 12 20:41:41 2006
@@ -1,7 +1,7 @@
 # This code is a part of Slash, and is released under the GPL.
-# Copyright 1997-2004 by Open Source Development Network. See README
+# Copyright 1997-2005 by Open Source Technology Group. See README
 # and COPYING for more information, or see http://slashcode.com/.
-# $Id: Environment.pm,v 1.10 2005/10/17 10:35:15 sugi Exp $
+# $Id: Environment.pm,v 1.11 2006/07/12 11:41:41 sugi Exp $
 
 package Slash::Utility::Environment;
 
@@ -28,13 +28,12 @@
 use Apache::ModuleConfig;
 use Digest::MD5 'md5_hex';
 use Time::HiRes;
-use Encode;
-use Encode::Guess;
+use Socket qw( inet_aton inet_ntoa );
 
 use base 'Exporter';
 use vars qw($VERSION @EXPORT);
 
-($VERSION) = ' $Revision: 1.10 $ ' =~ /\$Revision:\s+([^\s]+)/;
+($VERSION) = ' $Revision: 1.11 $ ' =~ /\$Revision:\s+([^\s]+)/;
 @EXPORT	   = qw(
 
 	dbAvailable
@@ -70,10 +69,12 @@
 	getObject
 	getAnonId
 	isAnon
+	isAdmin
 	isSubscriber
 	prepareUser
 	filter_params
-	get_ipids
+	loadClass
+	loadCoderef
 
 	setUserDate
 	isDST
@@ -81,6 +82,9 @@
 	bakeUserCookie
 	eatUserCookie
 	setCookie
+	getPublicLogToken
+
+	getPollVoterHash
 
 	debugHash
 	slashProf
@@ -94,6 +98,16 @@
 
 	determineCurrentSkin
 
+	get_ipids
+	get_srcids
+	convert_srcid
+	get_srcid_prependbyte
+	get_srcid_sql_in
+	get_srcid_sql_out
+	get_srcid_type
+	get_srcid_vis
+	decode_srcid_prependbyte
+
 );
 
 use constant DST_HR  => 0;
@@ -1049,7 +1063,7 @@
 	my $slashdb = getCurrentDB();
 	my $skins = $slashdb->getSkins();
 	for my $skid (keys %$skins) {
-		return 1 if $uid == $skins->{$skid}{ac_uid};
+		return 1 if $skins->{$skid}{ac_uid} && $uid == $skins->{$skid}{ac_uid};
 	}
 
 	# Nope, this UID is not anonymous.
@@ -1058,6 +1072,47 @@
 
 #========================================================================
 
+=head2 isAdmin(UID)
+
+Tests to see if the uid passed in is an admin.
+
+=over 4
+
+=item Parameters
+
+=over 4
+
+=item UID
+
+Value UID.  Can also be standard C<$user> hashref.
+
+=back
+
+=item Return value
+
+Returns true if the UID is an admin, otherwise false.
+
+=back
+
+=cut
+
+sub isAdmin {
+	my($user) = @_;
+	my $slashdb = getCurrentDB();
+	my $constants = getCurrentStatic();
+
+	# $user can be real $user, or $uid
+	my $usercheck = ref $user
+		? $user
+		: $slashdb->getUser($user, [qw(seclev)]);
+
+	return $usercheck->{seclev} >= 100
+		? 1
+		: 0;
+}
+
+#========================================================================
+
 =head2 isSubscriber(USER)
 
 Tests to see if the user passed in is a subscriber.
@@ -1078,7 +1133,10 @@
 
 =item Return value
 
-Returns true if the USER is a subscriber, otherwise false.
+Returns true if the USER is a subscriber, otherwise false.  Also returns
+true if the C<subscribe> var is false (everyone is a subscriber if there
+are no subscriptions), so check in your caller if you need subscriptions
+turned on.
 
 =back
 
@@ -1094,7 +1152,7 @@
 	if ($constants->{subscribe}) {
 		if (! ref $suser) {
 			my $slashdb = getCurrentDB();
-			$suser = $slashdb->getUser($suser);
+			$suser = $slashdb->getUser($suser, [qw(hits_paidfor hits_bought)]);
 		}
 
 		$subscriber = 1 if $suser->{hits_paidfor} &&
@@ -1110,8 +1168,7 @@
 
 =head2 getAnonId([FORMKEY])
 
-Creates an anonymous ID that is used to set an AC cookie,
-with some random data (well, as random as random gets)
+Returns a string of random alphanumeric characters.
 
 =over 4
 
@@ -1119,11 +1176,15 @@
 
 =over 4
 
-=item FORMKEY
+=item NOPREFIX
 
-Return the same value as normal, but without prepending with a '-1-'.
-The normal case, with '-1-', is for easy identification of cookies.
-This case is for use with formkeys.
+Don't prepend a "-1-" string. That prefix is no longer used anywhere in
+the code, so basically everyplace this function is used passes in true
+for noprefix.  All part of the slow evolution of the codebase!
+
+=item COUNT
+
+Number of characters (default 10).
 
 =back
 
@@ -1138,9 +1199,11 @@
 {
 	my @chars = (0..9, 'A'..'Z', 'a'..'z');
 	sub getAnonId {
-		my $str;
-		$str = '-1-' unless $_[0];
-		$str .= join('', map { $chars[rand @chars] }  0 .. 9);
+		my($noprefix, $count) = @_;
+		$count ||= 10;
+		my $str = "";
+		$str = '-1-' unless $noprefix;
+		$str .= join('', map { $chars[rand @chars] } 1 .. $count);
 		return $str;
 	}
 }
@@ -1166,7 +1229,8 @@
 
 =item VALUE
 
-Cookie's value.
+Cookie's value.  This used to be called 'passwd' but the value that gets
+put into user cookies now isn't a password anymore.
 
 =back
 
@@ -1180,8 +1244,8 @@
 
 # create a user cookie from ingredients
 sub bakeUserCookie {
-	my($uid, $passwd) = @_;
-	my $cookie = $uid . '::' . $passwd;
+	my($uid, $value) = @_;
+	my $cookie = $uid . '::' . $value;
 	return $cookie;
 }
 
@@ -1219,8 +1283,8 @@
 	my($cookie) = @_;
 	$cookie =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/ge
 		unless $cookie =~ /^\d+::/;
-	my($uid, $passwd) = split(/::/, $cookie, 2);
-	return($uid, $passwd);
+	my($uid, $value) = split(/::/, $cookie, 2);
+	return($uid, $value);
 }
 
 #========================================================================
@@ -1246,7 +1310,11 @@
 
 =item SESSION
 
-Flag to determine if the cookie should be a session cookie.
+Flag to determine if the cookie should be a session cookie.  "1" means
+yes, expire it after the current session.  "2" means to expire it
+according to the login_temp_minutes var.  And a value that looks like
+a session time, like "+24h", is passed along directly (in that case,
+expires 24 hours from now).
 
 =back
 
@@ -1301,6 +1369,8 @@
 	# lines, and uncomment the one right above "bake"
 	if (!$val) {
 		$cookie->expires('-1y');  # delete
+	} elsif ($session && $session =~ /^\+\d+[mhdy]$/) {
+		$cookie->expires($session);
 	} elsif ($session && $session > 1) {
 		my $minutes = $constants->{login_temp_minutes};
 		$cookie->expires("+${minutes}m");
@@ -1318,6 +1388,48 @@
 
 #========================================================================
 
+=head2 getPollVoterHash([UID])
+
+=cut
+
+sub getPollVoterHash {
+	my $constants = getCurrentStatic();
+	my $remote_addr = $ENV{REMOTE_ADDR} || '';
+        if ($constants->{poll_fwdfor} && $ENV{HTTP_X_FORWARDED_FOR}) {
+                return md5_hex($remote_addr . $ENV{HTTP_X_FORWARDED_FOR});
+        } else {
+                return md5_hex($remote_addr);
+        }
+}
+
+#========================================================================
+
+=head2 getPublicLogToken([UID])
+
+Just a wrapper around:
+
+	bakeUserCookie($uid, $slashdb->getLogToken($uid, 1, 2));
+
+to get a public logtoken.  Uses current user's UID if none supplied.
+
+=cut
+
+sub getPublicLogToken {
+	my($uid) = @_;
+	$uid ||= getCurrentUser('uid');
+	if ($uid) {
+		my $slashdb = getCurrentDB();
+		my $logtoken = $slashdb->getLogToken($uid, 1, 2);
+		if ($logtoken) {
+			return bakeUserCookie($uid, $logtoken);
+		}
+	}
+	return '';
+}
+
+
+#========================================================================
+
 =head2 prepareUser(UID, FORM, URI [, COOKIES])
 
 This is called to initialize the user.  It is called from
@@ -1428,12 +1540,15 @@
 	}
 
 	$user->{state}{post}	= $method eq 'POST' ? 1 : 0;
-	@{$user}{qw[ipid subnetid classbid hostip]} = get_ipids($hostip);
+	$user->{srcids}		= get_srcids({ ip => $hostip, uid => $uid });
+	@{$user}{qw[ipid subnetid classbid hostip]} = get_ipids();
+#	@{$user}{qw[ipid subnetid classbid hostip]} = get_srcids({ ip => $hostip },
+#		{ return_only => [qw( ipid subnetid classbid ip )] });
 
 	my @defaults = (
 		['mode', 'thread'], qw[
 		savechanges commentsort threshold
-		posttype noboxes light
+		posttype noboxes lowbandwidth simpledesign
 	]);
 
 	for my $param (@defaults) {
@@ -1481,7 +1596,7 @@
 
 	if ($uri =~ m[^/$]) {
 		$user->{currentPage} = 'index';
-	} elsif ($uri =~ m{(?:/|\b)([^/]+)\.pl$}) {
+	} elsif ($uri =~ m{\b([^/]+)\.pl\b}) {
 		$user->{currentPage} = $1;
 	} else {
 		$user->{currentPage} = 'misc';
@@ -1501,8 +1616,7 @@
 
 	if ($constants->{subscribe}) {
 		# Decide whether the user is a subscriber.
-		$user->{is_subscriber} = 1 if $user->{hits_paidfor}
-			&& $user->{hits_bought} < $user->{hits_paidfor};
+		$user->{is_subscriber} = isSubscriber($user);
 		# Make other decisions about subscriber-related attributes
 		# of this page.  Note that we still have $r lying around,
 		# so we can save Subscribe.pm a bit of work.
@@ -1513,19 +1627,30 @@
 			$user->{state}{page_adless} = $subscribe->adlessPage($r, $user);
 		}
 	}
+	if (!$user->{is_subscriber} && $constants->{daypass}) {
+		# If the user is not a subscriber, they may still be
+		# _effectively_ a subscriber if they have a daypass.
+		my $daypass_db = getObject('Slash::Daypass', { db_type => 'reader' });
+		if ($daypass_db->userHasDaypass($user)) {
+#			$user->{is_subscriber} = 1;
+			$user->{has_daypass} = 1;
+			$user->{state}{page_plummy} = 1;
+			$user->{state}{page_buying} = 0;
+			$user->{state}{page_adless} = 0;
+print STDERR scalar(localtime) . " Env.pm $$ userHasDaypass uid=$user->{uid} cs=$constants->{subscribe} is=$user->{is_subscriber} cd=$constants->{daypass}\n";
+		}
+	}
+
 	if ($user->{seclev} >= 100) {
 		$user->{is_admin} = 1;
 		# can edit users and do all sorts of cool stuff
 		$user->{is_super_admin} = 1 if $user->{seclev} >= 10_000 || $user->{acl}{super_admin};
-		my $sid;
-		#This cookie could go, and we could have session instance
-		#do its own thing without the cookie. -Brian
-		if ($cookies->{session}) {
-			$sid = $slashdb->getSessionInstance($uid, $cookies->{session}->value);
-		} else {
-			$sid = $slashdb->getSessionInstance($uid);
-		}
-		setCookie('session', $sid) if $sid;
+
+		# cookie no longer used, remove it if it is there -- pudge
+		setCookie('session', '') if $cookies->{session};
+		# this no longer "gets" anything, it only sets -- pudge
+		$slashdb->getSessionInstance($uid);
+
 		if ($constants->{admin_check_clearpass}
 			&& !Slash::Apache::ConnectionIsSecure()) {
 			$user->{state}{admin_clearpass_thisclick} = 1;
@@ -1540,6 +1665,34 @@
 		$user->{state}{lostprivs} = 1;
 	}
 
+	if ($constants->{plugin}{Tags}) {
+		my $write = $constants->{tags_stories_allowwrite} || 0;
+		$user->{tags_canwrite_stories} = 0;
+		$user->{tags_canwrite_stories} = 1 if
+			!$user->{is_anon} && (
+				   $write >= 4
+				|| $write >= 3 && $user->{karma} >= 0
+				|| $write >= 2.5 && $user->{acl}{tags_stories_allowwrite}
+				|| $write >= 2 && $user->{is_subscriber}
+				|| $write >= 1 && $user->{is_admin}
+			);
+		my $read;
+		if ($user->{tags_canwrite_stories}) {
+			$user->{tags_canread_stories} = 1;
+		} else {
+			$read = $constants->{tags_stories_allowread} || 0;
+			$user->{tags_canread_stories} = 0;
+			$user->{tags_canread_stories} = 1 if
+				!$user->{is_anon} && (
+					   $read >= 4
+					|| $read >= 3 && $user->{karma} >= 0
+					|| $read >= 2.5 && $user->{acl}{tags_stories_allowread}
+					|| $read >= 2 && $user->{is_subscriber}
+					|| $read >= 1 && $user->{is_admin}
+				);
+		}
+	}
+
 	return $user;
 }
 
@@ -1587,37 +1740,6 @@
 
 #========================================================================
 
-sub get_ipids {
-	my($hostip, $no_md5, $locationid) = @_;
-
-	$locationid = getCurrentStatic('cookie_location') if @_ > 2 && !$locationid;
-
-	if (!$hostip && $ENV{GATEWAY_INTERFACE}) {
-		my $r = Apache->request;
-		$hostip = $r->connection->remote_ip;
-	} elsif (!$hostip) {
-		$hostip = '';
-	}
-
-	my $ipid = $no_md5 ? $hostip : md5_hex($hostip);
-	(my $subnetid = $hostip) =~ s/(\d+\.\d+\.\d+)\.\d+/$1\.0/;
-	$subnetid = $no_md5 ? $subnetid : md5_hex($subnetid);
-	(my $classbid = $hostip) =~ s/(\d+\.\d+)\.\d+\.\d+/$1\.0\.0/;
-	$classbid = $no_md5 ? $classbid : md5_hex($classbid);
-
-	if ($locationid) {
-		return $locationid eq 'classbid' ? $classbid
-		     : $locationid eq 'subnetid' ? $subnetid
-		     : $locationid eq 'ipid'     ? $ipid
-		     : $locationid eq 'ip'       ? $hostip
-		     : '';
-	}
-
-	return($ipid, $subnetid, $classbid, $hostip);
-}
-
-#========================================================================
-
 =head2 filter_params(PARAMS)
 
 This cleans up form data before it is used by the program.
@@ -1672,12 +1794,24 @@
 		url_id spider_id miner_id keyword_id
 		st_main_select stc_main_select
 		parent_topic child_topic
+		skid primaryskid
+	),
+	# Survey
+	qw(
+		svid sqid ordnum next_sqid condnext_sqid sqcid
+		owneruid discussionid uid_min uid_max
+		expyear expmonth expday exphour expmin
+		openyear openmonth openday openhour openmin
 	);
 
 	# fields that have ONLY a-zA-Z0-9_
 	my %alphas = map {($_ => 1)} qw(
 		fieldname formkey commentstatus filter
-		hcanswer mode op section thisname type
+		hcanswer mode op section thisname type reskey
+	),
+	# Survey
+	qw(
+		svsid
 	);
 
 	# regexes to match dynamically generated numeric fields
@@ -1719,8 +1853,6 @@
 				$form{$_} = \@multi;
 				$form{_multi}{$_} = \@multi;
 				next;
-			} elsif (/^op_(.+)$/){
-				$form{'op'} = $1;
 			}
 		}
 	}
@@ -1753,8 +1885,6 @@
 
 sub filter_param {
 	my($key, $data) = @_;
-	my @candencs = qw/utf8 ascii euc-jp cp932 7bit-jis/;
-	my $enc;
 
 	# Paranoia - Clean out any embedded NULs. -- cbwood
 	# hm.  NULs in a param() value means multiple values
@@ -1772,18 +1902,6 @@
 		for my $ri (@regints) {
 			$data = fixint($data) if /$ri/;
 		}
-
-		# convert input to internal character encoding
-		# don't do any conversion if code can't be guessed
-		while ( !ref($enc) && @candencs){
-			$enc = guess_encoding( $data, @candencs );
-			if (ref($enc)){
-				$data = $enc->decode( $data );
-				last;
-			}else{
-				pop( @candencs );
-			}
-		}
 	}
 
 	return $data;
@@ -2092,14 +2210,22 @@
 	# clean up dangerous characters
 	$class =~ s/[^\w:]+//g;
 
-	# only if passed a hash, or no passed data at all
+	# Determine the db_type and the virtual user, based on the data
+	# passed in (if any), the 'classes' var (see getClasses()),
+	# and $user->{state}{dbs}.
 	if (!$data || ref $data eq 'HASH') {
+		# If we were passed a hash, or no data at all...
 		$data ||= {};
 		if ($data->{virtual_user}) {
 			$vuser = $data->{virtual_user};
 
 		} else {
-			# this really isn't used and is not well-tested anymore -- pudge
+			# For now, the $class specified is really only used to
+			# bless the returned object.  In theory setting rows in
+			# the classes table properly (which ends up in the var
+			# named 'classes') should also provide correct db_type
+			# and fallback data for that class.  But the classes
+			# table hasn't been tested in production.
 			my $classes = getCurrentStatic('classes');
 
 			# try passed db first, then db for given class
@@ -2111,15 +2237,14 @@
 
 			return undef if $db_type && $fallback && !$vuser;
 		}
-	}
-
-	# if plain string, use it as vuser
-	elsif (!ref $data) {
+	} elsif (!ref $data) {
+		# If we were passed a plain string, use that as
+		# the virtual user...
 		$data = { virtual_user => $data };
 		$vuser = $data->{virtual_user};
 	}
 
-	# in the future, we may default to something else, but for now it is the writer
+	# The writer is the logical DB to default to if nothing is specified.
 	$vuser ||= $user->{state}{dbs}{writer} || getCurrentVirtualUser();
 	return undef unless $vuser && $class;
 
@@ -2141,11 +2266,7 @@
 		}
 
 	} else {
-		# see if module has been loaded in already ...
-		(my $file = $class) =~ s|::|/|g;
-		# ... because i really hate eval
-		local $@; # $@ won't get cleared unless eval is performed
-		eval "require $class" unless exists $INC{"$file.pm"};
+		loadClass($class);
 
 		if ($@) {
 			errorLog($@);
@@ -2165,6 +2286,65 @@
 	}
 }
 
+{
+my %classes;
+sub loadClass {
+	my($class) = @_;
+
+	if ($classes{$class}) {
+		if ($classes{$class} eq 'NA') {
+			return 0;  # previous failure
+		}
+		return $classes{$class};  # previous success
+	}
+
+	# see if module has been loaded in already ...
+	(my $file = $class) =~ s|::|/|g;
+	# ... because i really hate eval
+	undef $@;  # for good measure
+	eval "require $class" unless exists $INC{"$file.pm"};
+
+	if ($@) {
+		$classes{$class} = 'NA';
+		return 0;
+	} else {
+		return $classes{$class} = 1;
+	}
+}
+}
+
+{
+my %coderefs;
+sub loadCoderef {
+	my($class, $function) = @_;
+	my $full = $class . '::' . $function;
+
+
+	if ($coderefs{$full}) {
+		if ($coderefs{$full} eq 'NA') {
+			return 0;  # previous failure
+		}
+		return $coderefs{$full};  # previous success
+	}
+
+	return 0 unless loadClass($class);
+
+	my $code;
+	{
+		no strict 'refs';
+		$code = \&{ $full };
+	}
+
+	if (defined &$code) {
+		return $coderefs{$full} = $code;
+	} else {
+		$coderefs{$full} = 'NA';
+		return 0;
+	}
+}
+}
+
+
 #========================================================================
 
 =head2 errorLog()
@@ -2187,6 +2367,11 @@
 sub errorLog {
 	return if $Slash::Utility::NO_ERROR_LOG;
 
+	# if set to 0, goes until no more callers found
+	my $max_level = defined $Slash::Utility::MAX_ERROR_LOG_LEVEL
+		? $Slash::Utility::MAX_ERROR_LOG_LEVEL
+		: 2;
+
 	my $level = 1;
 	$level++ while (caller($level))[3] =~ /log/i;  # ignore other logging subs
 
@@ -2194,8 +2379,13 @@
 
 	($package, $filename, $line) = caller($level++);
 	push @errors, ":$package:$filename:$line:@_";
-	($package, $filename, $line) = caller($level++);
-	push @errors, "Which was called by:$package:$filename:$line:@_" if $package;
+	$max_level--;
+
+	while ($max_level--) {
+		($package, $filename, $line) = caller($level++);
+		last unless $package;
+		push @errors, "Which was called by:$package:$filename:$line";
+	}
 
 	if ($ENV{GATEWAY_INTERFACE} && (my $r = Apache->request)) {
 		$errors[0] = $ENV{SCRIPT_NAME} . $errors[0];
@@ -2237,7 +2427,8 @@
 
 sub writeLog {
 	return unless $ENV{GATEWAY_INTERFACE};
-	my $dat = join("\t", @_);
+	my @args = grep { defined $_ } @_;
+	my $dat = @args ? join("\t", @args) : '';
 
 	my $r = Apache->request;
 
@@ -2285,6 +2476,7 @@
 	} elsif ($uri =~ /\.png$/) {
 		$uri = 'image';
 	} elsif ($uri =~ /\.rss$/ || $uri =~ /\.xml$/ || $uri =~ /\.rdf$/ || $ENV{QUERY_STRING} =~ /\bcontent_type=rss\b/) {
+		$dat = $uri;
 		$uri = 'rss';
 	} elsif ($uri =~ /\.pl$/) {
 		$uri =~ s|^/(.*)\.pl$|$1|;
@@ -2338,10 +2530,21 @@
 	my $logdb = getObject('Slash::DB', { virtual_user => $constants->{log_db_user} });
 
 	my($op, $new_dat) = getOpAndDatFromStatusAndURI($status, $uri, $dat);
+	my $user = getCurrentUser();
+
+	# Always log this hit to accesslog, unless this is an admin hitting
+	# admin.pl and the log_admin var has been set to false (this will
+	# help small sites keep admin traffic out of that log, which really
+	# is of questionable value since crawlers will be just as bad, but
+	# hey, we offer site admins the option anyway).
+	unless (!$constants->{log_admin} && $user->{is_admin} && $uri =~ /admin\.pl/) {
+		$logdb->createAccessLog($op, $new_dat, $status);
+	}
 
-	$logdb->createAccessLog(	$op, $new_dat, $status);
-	$logdb->createAccessLogAdmin(	$op, $new_dat, $status)
-		if getCurrentUser('is_admin');
+	# If this is an admin user, all hits go into accesslog_admin.
+	if ($user->{is_admin}) {
+		$logdb->createAccessLogAdmin($op, $new_dat, $status);
+	}
 }
 
 #========================================================================
@@ -2464,29 +2667,22 @@
 	my $skin;
 
 	if ($ENV{GATEWAY_INTERFACE} && (my $r = Apache->request)) {
-		my $hostname = $r->header_in('host');
+		my $hostname = $r->header_in('host') || '';
 		$hostname =~ s/:\d+$//;
  
-		# filter skins by hostname
 		my $skins = $reader->getSkins;
-		my @potSkin = grep { lc $skins->{$_}{hostname} eq lc $hostname }
-			sort { $a <=> $b } keys %$skins;
-
-		# match /section/...,  / is matched to 
-		if ($r->uri() =~ m|^/(\w+)/|){
-			my $key = $1;
-			($skin) = grep {$skins->{$_}{name} eq $key } @potSkin;
-		}else{
-			($skin) = @potSkin;
-		}
+		($skin) = grep {
+				(my $tmp = lc $skins->{$_}{hostname} || '') =~ s/:\d+$//;
+				$tmp eq lc $hostname
+			} sort { $a <=> $b } keys %$skins;
 
 		# don't bother warning if $hostname is numeric IP
 		if (!$skin && $hostname !~ /^\d+\.\d+\.\d+\.\d+$/) {
 			$skin = getCurrentStatic('mainpage_skid');
 			if (!$skin) {
-				errorLog("determineCurrentSkin called but no skin found (even default) for $hostname\n");
+				errorLog("determineCurrentSkin called but no skin found (even default) for '$hostname'\n");
 			} else {
-				#errorLog("determineCurrentSkin called but no skin found (so using default) for $hostname\n");
+				errorLog("determineCurrentSkin called but no skin found (so using default) for '$hostname'\n");
 			}
 		}
 	} else {
@@ -2503,6 +2699,496 @@
 }
 
 #========================================================================
+# XXXSRCID eliminate this or bring it back or something
+sub get_ipids {
+	my($hostip, $no_md5, $locationid) = @_;
+ 
+	$locationid = getCurrentStatic('cookie_location') if @_ > 2 && !$locationid;
+ 
+	if (!$hostip && $ENV{GATEWAY_INTERFACE}) {
+		my $r = Apache->request;
+		$hostip = $r->connection->remote_ip;
+	} elsif (!$hostip) {
+		$hostip = '';
+	}
+ 
+	my $ipid = $no_md5 ? $hostip : md5_hex($hostip);
+	(my $subnetid = $hostip) =~ s/(\d+\.\d+\.\d+)\.\d+/$1\.0/;
+	$subnetid = $no_md5 ? $subnetid : md5_hex($subnetid);
+	(my $classbid = $hostip) =~ s/(\d+\.\d+)\.\d+\.\d+/$1\.0\.0/;
+	$classbid = $no_md5 ? $classbid : md5_hex($classbid);
+ 
+	if ($locationid) {
+		return $locationid eq 'classbid' ? $classbid
+		     : $locationid eq 'subnetid' ? $subnetid
+		     : $locationid eq 'ipid'     ? $ipid
+		     : $locationid eq 'ip'       ? $hostip
+		     : '';
+	}
+ 
+	return($ipid, $subnetid, $classbid, $hostip);
+}
+
+#========================================================================
+
+=head2 get_srcids
+
+Converts an IP address and/or user id to a hashref containing one or
+more srcids.
+
+=over 4
+
+=item Parameters
+
+=over 4
+
+=item DATA
+
+A hashref containing one or more of two possible fields:  (1) "uid",
+whose value is a user id;  and/or (2) "ip", whose value is a text string
+representing an IPv4 address in the form of "1.2.3.4" (IPv6 is not
+yet supported), or a false value which means to use the current Apache
+connection's remote_ip if invoked within Apache, or the dummy value
+"0.0.0.0" otherwise.
+
+=item OPTIONS
+
+An optional hashref containing zero or more options.  The option
+'no_md5', if its field is any true value, ensures that IPs encoded into
+the returned hashref, while still masked, are not MD5'd but kept in string
+"1.2.3.4" form.
+
+The option 'masks' can be a scalar containing a single value or an
+arrayref containing multiple values.  By default, only two masked-off
+values of an IP are encoded:  a 32-bit and a 24-bit mask (the IP itself
+and its Class C subnet).  Any additional values between 1 and 32 may be
+passed in in 'masks' and those additional mask sizes will also be
+calculated and encoded in the returned hashref.
+
+The option 'return_only' will change the returned value from a hashref
+with multiple fields into an array which contains the values of one or
+more of what those fields would have been.  The value(s) of 'return_only'
+can be: (1) the string "uid" to return just the uid, (2) an integer
+between 1 and 32 (which will be implied into the 'masks' option as well),
+(3) one of the three strings "ipid", "subnetid", or "classbid" which
+are the equivalent of the integers 32, 24, and 16 respectively, or (4)
+the string "cookie_location", which will be replaced by the value of
+the var 'cookie_location'.
+
+=back
+
+=item Return value
+
+The uid and/or ip converted to an encoded hashref.  If a uid was passed
+in, the field "uid" stores the converted uid (which happens to be the
+uid itself).  If an ip was passed in, there will be one or more fields
+whose names are integer values between 1 and 32 and whose values are
+64-bit (16-char) lowercase hex strings with the encoded values of the ip.
+There will also be the convenience field "ip" set which contains the
+original ip value passed in.  (But see OPTIONS:  if return_only is set,
+only one of the fields of the hashref will be returned.)
+
+=back
+
+=cut
+
+sub get_srcids {
+	my($hr, $options) = @_;
+	$hr ||= { };
+	my $retval = { };
+	my($no_md5, $return_only, $masks) = _get_srcids_options($options);
+
+	# UIDs are the easy part.  The encoded value of a uid is the uid
+	# itself, in decimal form, so we just pass this input along to
+	# the output.
+	if (my $uid = $hr->{uid}) {
+		$retval->{uid} = $uid;
+	}
+
+	# IPs are the tricky part.
+	if (defined(my $ip = $hr->{ip})) {
+		if (!defined($no_md5)) {
+			# Error in options passed.
+			warn "Error in options passed to get_srcids";
+#use Data::Dumper; $Data::Dumper::Sortkeys=1; print STDERR "options: " . Dumper($options);
+			return undef;
+		}
+		if (!$ip) {
+			if ($ENV{GATEWAY_INTERFACE}) {
+				my $r = Apache->request;
+				$ip = $r->connection->remote_ip;
+			} elsif (!$ip) {
+				$ip = '0.0.0.0';
+			}
+		}
+
+		$retval->{ip} = $ip; # return the IP passed in, for convenience
+
+		# For each entry in @$masks, we truncate the IPv4 address
+		# to that many bits (so 32 is the whole IP address, this
+		# works the same way as a /32 mask).  Then convert to
+		# a text string of the dotted-quad, truncate to the
+		# proper width, and prepend the code indicating what kind
+		# of mask has been applied.
+
+		# XXX For IPv6, here's where we need to decide how to
+		# extend the value of 'masks' to a 128-bit IP address.
+		# For IPv4, we convert the string "1.2.3.4" to the
+		# number 0x01020304, mask it, then convert back to
+		# dotted-quad string.
+		my $n = unpack("N", inet_aton($ip));
+
+		for my $mask (@$masks) {
+			my $prepend_code = get_srcid_prependbyte({
+				type =>         'ipid',
+				mask =>         $mask,
+			});
+			my $bitmask = ~( 2**(32-$mask) - 1 );
+			my $nm = $n & $bitmask;
+			my $str = inet_ntoa(pack("N", $nm));
+			my $val;
+			if ($no_md5) {
+				$val = $str;
+			} else {
+				my $md5 = md5_hex($str);
+				my $md5_trunc = substr($md5, 0, 14);
+				$val = lc("$prepend_code$md5_trunc");
+			}
+			$retval->{$mask} = $val;
+		}
+
+	}
+
+	if ($return_only) {
+		# Return just the value(s) requested, no hashref
+		# wrapper around them.
+		my @retvals = ( );
+		for my $field (@$return_only) {
+			push @retvals, $retval->{$field};
+		}
+		return @retvals;
+	}
+
+	return $retval;
+}
+
+{ # closure
+my %mask_size_name = (
+	ipid =>         32,
+	subnetid =>     24,
+	classbid =>     16,
+);
+
+sub convert_srcid {
+	my($type, $old_id_str) = @_;
+	# UIDs are easy, they haven't changed.
+	return $old_id_str if $type eq 'uid';
+	# IPs, Subnets, and Class B's get converted to MD5s and then
+	# treated like IPIDs, SubnetIDs, and ClassBIDs..
+	if ($mask_size_name{"${type}id"}) {
+		$old_id_str = md5_hex($old_id_str);
+		$type = "${type}id";
+	}
+	# IPIDs, SubnetIDs, and ClassBIDs get a prepended byte and a
+	# truncate to convert them..
+	if ($mask_size_name{$type}) {
+		my $str_trunc = lc(substr($old_id_str, 0, 14));
+		my $prependbyte = get_srcid_prependbyte({
+			type => 'ipid',
+			mask => $mask_size_name{$type}
+		});
+		return "$prependbyte$str_trunc";
+	}
+	# XXXSRCID this is a logic error but handle it better
+	die "logic error convert_srcid: type='$type' ois='$old_id_str'";
+}
+
+sub _get_srcids_options {
+	my($options) = @_;
+	my $no_md5 = $options->{no_md5} || 0;
+
+	my $return_only = '';
+	if (defined $options->{return_only}) {
+		# Pass in no defined value for the return_only field,
+		# and get_srcids returns the whole hashref.
+		# Pass in a defined value which is either a common
+		# name of a mask size, or an integer indicating a
+		# mask size, and this function returns only that one
+		# value out of the hashref.
+		# Pass in the word 'cookie_location', and this function
+		# returns only the one value named in the var 'cookie_location'.
+		if ($options->{return_only} eq 'cookie_location') {
+			$return_only = getCurrentStatic('cookie_location');
+		} else {
+			$return_only = $options->{return_only};
+		}
+		my %ro = ( );
+		my @k = ref($return_only) ? @$return_only : ( $return_only );
+		my @ret = ( );
+		for my $k (@k) {
+			if ($k =~ /^\d+$/ && $k >= 1 && $k <= 32) {
+				# It's already a valid number indicating mask size.
+				push @ret, $k;
+			} else {
+				if ($mask_size_name{$k}) {
+					# It's the name of a valid mask.
+					push @ret, $mask_size_name{$k};
+				} elsif ( $mask_size_name{"${k}id"} ) {
+					# It's the name of a valid mask
+					# minus the "id", which is close
+					# enough.
+					push @ret, $mask_size_name{"${k}id"};
+				} else {
+					# Invalid value, abort.
+#use Data::Dumper; $Data::Dumper::Sortkeys=1; print STDERR "k='$k' options: " . Dumper($options);
+					return undef;
+				}
+			}
+		}
+		$return_only = \@ret;
+	}
+
+	my $masks = [ ];
+	if ($return_only) {
+		# If we've been asked to return specific values,
+		# calculate just those values.
+		$masks = [ @$return_only ];
+	} else {
+		if (defined $options->{masks}) {
+			$masks = $options->{masks};
+			if (!ref $masks) {
+				$masks = [ $masks ];
+			}
+		}
+		@$masks = sort grep { /^\d+$/ } @$masks;
+		if (!@$masks) {
+			# Bad or no option passed in;  use default.
+			# XXXSRCID This should be a var.
+			$masks = [qw( 16 24 32 )];
+		}
+	}
+
+	# Return the options the caller's caller asked for.
+	return ($no_md5, $return_only, $masks);
+}
+} # closure
+
+#========================================================================
+
+=head2 get_srcid_prependbyte
+
+This returns the two-character hex code that should be prepended
+to a 14-character hex value to create the 16-character hex value
+representing either a user ID or an encoded IP address MD5.
+
+=over 4
+
+=item Parameters
+
+=over 4
+
+=item PARAMS
+
+A hash of the parameters.  'type' is a required parameter; its only
+defined values so far are 'ipid', indicating an encoded IP address of
+some type, or 'uid', indicating a user ID.  Other values may be
+possible in future.
+
+=over 4
+
+If 'type' eq 'ipid', the parameter 'mask' must be present, a number
+from 1 to 32 indicating how many bits will be present.  For example,
+a 24 here would have the same meaning as in '192.168.0.0/24',
+signifying the address is a Class C.
+
+=back
+
+=over 4
+
+If 'type' eq 'uid', no other parameters are necessary.
+
+=back
+
+=back
+
+=item Return value
+
+Two-character hex code to prepend.  The bit values of this code are
+currently defined as follows:
+
+=over 4
+
+bits 0-2 (MSBs): 0b000=uid; 0b001=IPv4 ipid; other values reserved
+for future use.
+
+bits 3-7 (LSBs): if uid, all 0; if IPv4 ipid, 32 minus the mask
+size (so 0b00000 indicates a mask size of 32, 0b01000 24, etc.)
+
+=back
+
+Thus the most commonly returned values will be: "00" = uid,
+"20" = ipid, "28" = subnetid, "30" = classbid.
+
+=cut
+
+sub get_srcid_prependbyte {
+	my($hr) = @_;
+	my $val = 0;
+	my $type = $hr->{type};
+	if ($type eq 'ipid') {
+		my $mask32 = 32 - $hr->{mask};
+		$val = 0x20 + $mask32;
+	} else {
+		$val = 0x00;
+	}
+	return sprintf("%02x", $val);
+}
+
+#========================================================================
+
+=head2 decode_srcid_prependbyte(BYTE)
+
+Decodes the byte encoded by encode_prepend_byte.  Returns a
+hashref with fields 'type' and, if type is 'ipid', 'mask'.
+
+=cut
+
+sub decode_srcid_prependbyte {
+	my($byte) = @_;
+	my $val = hex($byte);
+	my $type = ($val & 0b11100000) >> 5;
+	if ($type == 0) {
+		return { type => 'uid' };
+	}
+	my $mask = 32 - ($val & 0b00011111);
+	return {
+		type => 'ipid',
+		mask => $mask,
+	};
+}
+
+#========================================================================
+
+=head2 get_srcid_sql_in
+
+Pass this a srcid, either in decimal form (which is what uids will
+typically be in) or as a 64-bit (16-char) hex string, and it will return
+an SQL function or value which can be used as part of a test against
+or an assignment into an SQL integer value.  This value should _not_ be
+quoted but rather inserted directly into an SQL request.  For example,
+if passed "123" (a user id), will return "'123'" (same value,
+quoted);  if passed "200123456789abcd" (an encoded IP), will return
+"CONV('200123456789abcd', 16, 10)" which can be used as an assignment
+into or test against a BIGINT column.
+
+For speed, does not do error-checking against the value passed in.
+
+Usage:
+
+$slashdb->sqlInsert("al2", { srcid => get_srcid_sql_in($srcid) });
+
+=cut
+
+sub get_srcid_sql_in {
+	my($srcid) = @_;
+	if ($srcid !~ /^[0-9a-f]+$/) {
+		my @caller_info = ( );
+		for (my $lvl = 1; $lvl < 99; ++$lvl) {
+			my @c = caller($lvl);
+			last unless @c;
+			next if $c[0] =~ /^Template/;
+			push @caller_info, "$c[0] line $c[2]";
+			last if scalar(@caller_info) >= 3;
+		}
+		warn "Invalid param to get_srcid_sql_in, callers: @caller_info";
+	}
+	my $slashdb = getCurrentDB();
+	my $srcid_q = $slashdb->sqlQuote($srcid);
+	my $type = get_srcid_type($srcid);
+	if ($type eq 'uid') {
+		return $srcid_q;
+	}
+	return "CONV($srcid_q, 16, 10)";
+}
+
+#========================================================================
+
+=head2 get_srcid_sql_out
+
+Pass this the name of a column with srcid data, and it returns the SQL
+necessary to retrieve data from that column in srcid format.  The
+column data is returned in decimal format if it can be represented in
+decimal in an ordinarily-compiled perl, as a hex string otherwise.
+"Non-decimal characters in the result will be uppercase," say the docs,
+so we lowercase them.
+
+Usage:
+
+$slashdb->sqlSelectColArrayref(get_srcid_sql_out('srcid') . ' AS srcid',
+'al2', 'value=1');
+
+=cut
+
+sub get_srcid_sql_out {
+	my($colname) = @_;
+	return "IF($colname < (1 << 31), $colname, LOWER(CONV($colname, 10, 16)))";
+}
+
+#========================================================================
+
+=head2 get_srcid_type
+
+Pass this a srcid, either in decimal form (which is what uids will
+typically be in) or as a 64-bit (16-char) hex string, and it will return
+the name of the field in a srcid hashref that the data belongs in: either
+"uid" for a uid, or an integer between 1 and 32 for an encoded IP.
+
+For speed, does not do error-checking against the value passed in.
+
+=cut
+
+sub get_srcid_type {
+	my($srcid) = @_;
+	my $is_hex = ( length($srcid) == 16 || $srcid !~ /^\d+$/ );
+	if (!$is_hex) {
+		# Only uids are allowed to be passed around in decimal
+		# form, so this must be a uid.
+		return 'uid';
+	}
+	# Read the code on the front of the string.
+	my $code = substr($srcid, 0, 2);
+	if ($code eq '00') {
+		# Hex-encoded UID.
+		return 'uid';
+	}
+	# That code indicates an IP, and its least significant 5 bits
+	# encode the size of the mask used on that IP, which we return.
+	my $decval = hex($code);
+	return $decval & 0b00011111;
+}
+
+#========================================================================
+
+=head2 get_srcid_vis
+
+Pass this a srcid, either in decimal form (which is what uids will
+typically be in) or as a 64-bit (16-char) hex string, and it will return
+a short text string suitable for display, typically as the text that
+is linked in HTML.
+
+For speed, does not do error-checking against the value passed in.
+
+=cut
+
+sub get_srcid_vis {
+	my($srcid) = @_;
+	my $type = get_srcid_type($srcid);
+	return $srcid if $type eq 'uid';
+	my $vislen = getCurrentStatic('id_md5_vislength') || 5;
+	return substr($srcid, 2, $vislen);
+}
+
+#========================================================================
 {my @prof;
 sub slashProf {
 	return unless getCurrentStatic('use_profiling');
@@ -2525,20 +3211,31 @@
 	my $first = $prof[0][0];
 	my $last  = $first;  # Matthew 20:16
 	my $end   = $prof[-1][0];
-	my $total = ($end - $first) * 1_000;
+	my $total = $end - $first;
 
 	$total ||= $first || $end || 1;  # just in case
 
+	my $unit  = ' s';
+	my $multi = 1;
+
+	if ($total < 100) {
+		$unit    = 'ms';
+		$multi  *= 1_000;
+		$total  *= 1_000;
+	}
+
+	local $\;
+
 	print STDERR "\n*** Begin profiling ($$)\n";
-	print STDERR "*** Begin ordered ($$)\n";
-	printf STDERR <<'EOT', "PID", "what", "this #", "pct", "tot. #", "pct";
-%-6.6s: %-64.64s % 6.6s ms (%6.6s%%) / % 6.6s ms (%6.6s%%)
+	print STDERR "*** Begin ordered ($$)\n" if $use_profiling > 1;
+	printf STDERR <<"EOT", "PID", "what", "this #", "pct", "tot. #", "pct" if $use_profiling > 1;
+%-6.6s: %-64.64s % 6.6s $unit (%6.6s%%) / % 6.6s $unit (%6.6s%%)
 EOT
 
 	my(%totals, %begin);
 	for my $prof (@prof) {
-		my $t1 = ($prof->[0] - $first) * 1_000;
-		my $t2 = ($prof->[0] - $last) * 1_000;
+		my $t1 = ($prof->[0] - $first) * $multi;
+		my $t2 = ($prof->[0] - $last) * $multi;
 		my $p1 = $t1 / $total * 100;
 		my $p2 = $t2 / $total * 100;
 		my $s1 = sprintf('%.2f', $p1);
@@ -2581,20 +3278,23 @@
 		# mark new beginning
 		$begin{$prof->[5]} = $t1 if $prof->[5];
 
-		printf STDERR <<'EOT', $$, $where, $t2, $s2, $t1, $s1 if $use_profiling > 1;
-%-6d: %-64.64s % 6d ms (%6.6s%%) / % 6d ms (%6.6s%%)
+		printf STDERR <<"EOT", $$, $where, $t2, $s2, $t1, $s1 if $use_profiling > 1;
+%-6d: %-64.64s % 6d $unit (%6.6s%%) / % 6d $unit (%6.6s%%)
 EOT
 	}
 
 	print STDERR "\n*** Begin summary ($$)\n";
-	printf STDERR <<'EOT', "PID", "what", "time", "pct";
-%-6.6s: %-64.64s % 6.6s ms (%6.6s%%)
+	printf STDERR <<"EOT", "PID", "what", "time", "pct";
+%-6.6s: %-64.64s % 6.6s $unit (%6.6s%%)
+EOT
+	printf STDERR <<"EOT", $$, 'total', $total, '100.00';
+%-6d: %-64.64s % 6d $unit (%6.6s%%)
 EOT
 	for (sort { $totals{$b} <=> $totals{$a} } keys %totals) {
 		my $p = $totals{$_} / $total * 100;
 		my $s = sprintf('%.2f', $p);
-		printf STDERR <<'EOT', $$, $_, $totals{$_}, $s;
-%-6d: %-64.64s % 6d ms (%6.6s%%)
+		printf STDERR <<"EOT", $$, $_, $totals{$_}, $s;
+%-6d: %-64.64s % 6d $unit (%6.6s%%)
 EOT
 	}
 
@@ -2683,4 +3383,4 @@
 
 =head1 VERSION
 
-$Id: Environment.pm,v 1.10 2005/10/17 10:35:15 sugi Exp $
+$Id: Environment.pm,v 1.11 2006/07/12 11:41:41 sugi Exp $


Slashdotjp-dev メーリングリストの案内
Back to archive index