Apache PerlAuthzHandler and form POST vars - apache

I have a mod_perl PerlAuthzHandler authorizing access to directories served by Apache2. In terms of controlling access it is working well.
A side effect, though, is that it seems to be preventing POST form variables from being available to PHP apps in the protected locations. At least when I comment out the PerlAuthzHandler and reload Apache the PHP app functions again. I think either the full environment is not being inherited by Perl or Perl is sending a cleansed environment on.
Is it possible to ensure that all POST variables are available to the PHP application after authorization?
Details
User authentication is managed by simplesamlphp on this box, the SP, and ADFS, the IdP. The addition to the simplesamlphp installation is the use of PerlAuthzHandler.
Apache configuration
The Apache configuration for the location in question looks like this:
<Location /activity>
ErrorDocument 401 "/simplesaml/authmemcookie.php"
AuthName "MCLAnet"
AuthType Cookie
Require valid-user
PerlSetvar Auth_memCookie_SessionTableSize "40"
PerlAuthzHandler My::simple
PerlSetVar VALID_GROUP_EXPR "status-acad or staff-g"
</Location>
Authorization handler
The authorization handler retrieves group memberships recorded in setting up the session and compared to a VALID_GROUP_EXPRESSION in the PerlSetVar:
#! /usr/bin/perl
package My::simple;
# Version 1.0 MCLA CSS
use Apache2::Access ();
use Apache2::RequestUtil ();
# load modules that are going to be used
use Data::Dumper;
use CGI qw(:standard);
use CGI::Cookie;
use Cache::Memcached;
use PHP::Serialization qw(serialize unserialize);
# compile (or import) constants
use Apache2::Const -compile => qw(OK FORBIDDEN);
$debug=0;
$debug_file="/tmp/xxx";
dmp('prerun',('test'));
sub handler {
my $r = shift;
my $user = $r->user;
dmp('0 user',$user);
# ------------------------ get valid group(s) for this session
my $valid_group_expr=$r->dir_config("VALID_GROUP_EXPR");
dmp('1 valid group list',$valid_group_expr);
# -- get the session cooke to retrieve the session <ID>
$query = new CGI;
# fetch existing cookies
my %cookies = CGI::Cookie->fetch;
# dmp('Cookies',%cookies);
my $ID = $cookies{'SimpleSAMLSessionID'}->value;
dmp('2 SimpleSAMLSessionID value',$ID);
my $SessionID='simpleSAMLphp.session.' . $ID;
# -- use the session ID to look up the value of memcached key simpleSAMLphp.session.<ID>
my $cache = new Cache::Memcached {
'servers' => ['127.0.0.1:11211'],
'compress_threshold' => 10_000,
};
# Get the value from cache:
my $value = $cache->get($SessionID);
# dmp('mamcache value',($value));
# -- use the value data to find the groups
my $hashref = unserialize($value);
# dmp('mamcache unserialized',($hashref));
my %hash = %{ $hashref };
%hash = % { $hash {'data'}{chr(0) . 'SimpleSAML_Session' . chr(0) . 'authData'}{'default-sp'}{'Attributes'} };
my #groups = # { $hash{'groups'} };
dmp("3 Comparing $valid_group_expr to", \#groups);
my $result=evaluate($valid_group_expr,#groups);
if ($result) {
dmp("this guy oK",$result);
return Apache2::Const::HTTP_OK;
}
dmp("blowing this guy off",$result);
$r->log_reason("Not a member of group " . $valid_group_expr);
return Apache2::Const::FORBIDDEN;
# return Apache2::Const::HTTP_FORBIDDEN;
# return Apache2::Const::HTTP_OK;
# return Apache2::Const::DECLINED;
}
# ======================= utility functions
# evaluate returns the boolean value of the expression $expr
# after substituting membership information in #groups
#
# valid operators are
#
# &&, and, AND logical AND
# ||, or, OR logical OR
# !, NOT, not logical NOT
#
# expression must be infix and precidence can be indicated by ()
sub evaluate {
my ($expr,#groups)=#_;
# print "$expr\n";
# print Dumper(\%group_hash);
# operator tokens
my %token_hash = (
'(' => '(',
')' => ')',
'AND' => '&&',
'and' => '&&',
'or' => '||',
'OR' => '||',
'!' => '!',
'not' => '!',
'NOT' => '!',
) ;
# add the group array into the token hash as TRUEs
foreach $v (#groups) {
$v=~s/ /_/g;
$token_hash{$v} = 1;
}
dmp('merged hash',\%token_hash);
# merge the two hashes into %token_hash
# foreach my $tkey ( keys %group_hash) { $token_hash{$tkey} = $group_hash{$tkey}; }
# print Dumper(\%token_hash);
$expr=~s/\(/ ( /g;
$expr=~s/\)/ ) /g;
$expr=~s/\!/ ! /g;
# print "$expr\n";
my #expr_hash=split (/ /,$expr);
$expr='';
foreach my $t (#expr_hash) {
if ($t ne '') {
if (exists ($token_hash{$t})) { $t = $token_hash{$t} } else {$t = 0;}
$expr = $expr . "$t ";
}
}
dmp("expression",$expr);
my $result=0;
my $assignment="\$result = $expr;";
dmp("assignment",$assignment);
eval($assignment);
dmp("result",$result);
return $result;
}
# debug dump structure funcion
sub dmp {
if ($debug == 1) {
my ($label,#value) = #_;
my $temp = Dumper(#value);
open (T, ">>$debug_file"); # || die "Can't open $debug_file: $!\n";
print T "$label: $temp\n";
close (T);
}
}
1;
Failing PHP script
The simple PHP form below displays no value when the Perl authorization is enabled. If I comment out the PerlAuthzHandler line both $_POST'[submit'] and $_POST['in1'] are set.
<?php
if (isset($_POST['submit'])) { print_r($_POST); }
$form=<<<EOT
<form name="test" action="y.php" method="post">
<input name="in1">
<input type="submit" name="submit">
</form>
EOT;
print $form;
?>
Again, authentication (simplesamlphp/ADFS) and authorization both work as expected. The exception is that when authorization is used no $_POST variables are available.

RESOLUTION
As often happens the problem was of my own making. One of the kind monks at perlmonks.org pointed out that the Perl handler contained the line:
$query = new CGI;
Removing that did it. Since I was in fact only accessing but not changing anything there was no point to it and in fact $query was never used.

Related

Is it possible to create subroutine signatures at run time?

I've got a method in a class:
method options(*#opt) {
if !#!valid-options {
my $out = (cmd 'bin/md2html -h').out;
my #matches = $out ~~ m:g/\s'--'(<-[\s]>+)/;
for #matches -> $opt {
push #!valid-options, $opt[0].Str;
}
}
for #opt -> $opt {
when !($opt (elem) #!valid-options) {
warn "'$opt' is not a valid option";
}
push #!options, '--' ~ $opt;
}
}
The method checks that the options to see if they are valid and, if they are, places them into an attribute.
I pass args into the options method like this, as words:
$obj.options: <ftables ftasklists github>;
This works. But it got me wondering if it was possible pass in the options as named flags like this:
$obj.options: :ftables, :ftasklists, :github
But since I don't know all the command's options ahead of time, I'd need to generate the named arguments dynamically. Is this possible? I tried this but had no luck:
# create a signature
my #params = Parameter.new(name => ':$option', type => Bool, :!default);
my $sig = Signature.new(:#params);
my &blah = -> $sig { say 'this works too' } ;
&blah(:option1);
Currently, there is no way to do that, short of using EVAL.
You can add a slurpy hash to any sub signature to catch all unexpected named arguments:
sub foo(*%_) { .say for %_.keys }
foo :bar, :baz; # bar baz
Creating your own signatures at runtime may become possible / easier when the RakuAST has landed.

Perl web server: How to route

As seen in my code below, I am using apache to serve my Perl web server. I need Perl to have multple routes for my client as seen in my %dispatch. If I figure one out I'm sure the rest will be very similar. If we look at my Subroutine sub resp_index, how can I modify this to link to my index.html file located in my root: /var/www/perl directory?
/var/www/perl/perlServer.pl:
#!/usr/bin/perl
{
package MyWebServer;
use HTTP::Server::Simple::CGI;
use base qw(HTTP::Server::Simple::CGI);
my %dispatch = (
'/index.html' => \&resp_index,
# ...
);
sub handle_request {
my $self = shift;
my $cgi = shift;
my $path = $cgi->path_info();
my $handler = $dispatch{$path};
if (ref($handler) eq "CODE") {
print "HTTP/1.0 200 OK\r\n";
$handler->($cgi);
} else {
print "HTTP/1.0 404 Not found\r\n";
print $cgi->header,
$cgi->start_html('Not found'),
$cgi->h1('Not found'),
$cgi->end_html;
}
}
sub resp_index {
my $cgi = shift; # CGI.pm object
return if !ref $cgi;
my $who = $cgi->param('name');
print $cgi->header,
$cgi->start_html("index"),
$cgi-h1("THIS IS INDEX"),
$cgi->end_html;
}
}
my $pid = MyWebServer->new()->background();
print "Use 'kill $pid' to stop server.\n";
I think what you're asking is how do you serve a file from your web server? Open it and print it, like any other file.
use autodie;
sub resp_index {
my $cgi = shift;
return if !ref $cgi;
print $cgi->header;
open my $fh, "<", "/var/www/perl/index.html";
print <$fh>;
}
Unless this is an exercise, really, really, REALLY don't write your own web framework. It's going to be slow, buggy, and insecure. Consider a small routing framework like Dancer.
For example, mixing documents like index.html and executable code like perlServer.pl in the same directory invites a security hole. Executable code should be isolated in their own directory so they can be given wholly different permissions and stronger protection.
Let's talk about this line...
return if !ref $cgi;
This line is hiding an error. If your functions are passed the wrong argument, or no argument, it will silently return and you (or the person using this) will have no idea why nothing happened. This should be an error...
use Carp;
croak "resp_index() was not given a CGI object" if !ref $cgi;
...but really you should use one of the existing function signature modules such as Method::Signatures.
use Method::Signatures;
func resp_index(CGI $cgi) {
...
}

Fixing :80 urls for Mediawiki with Nginx and apache

I'm trying to use Mediawiki, without getting the url to be like this.
https://roblox-network.com:80/tra/Special:RequestAccount
If i just remove :80 it works as it should.
Config file is below.
It should be a clean install, with restricted accounts.
But sadly i keep getting the :80 url many times, when i update every page.
So i have to remove :80 to update the pages.
<?php
error_reporting( E_ALL );
ini_set( 'display_errors', 1 );
# This file was automatically generated by the MediaWiki 1.17.0
# installer. If you make manual changes, please keep track in case you
# need to recreate them later.
#
# See includes/DefaultSettings.php for all configurable settings
# and their default values, but don't forget to make changes in _this_
# file, not there.
#
# Further documentation for configuration settings may be found at:
# http://www.mediawiki.org/wiki/Manual:Configuration_settings
# Protect against web entry
if ( !defined( 'MEDIAWIKI' ) ) {
exit;
}
## Uncomment this to disable output compression
# $wgDisableOutputCompression = true;
$wgSitename = 'TRA Wiki';
$wgMetaNamespace = "My_wiki";
$wgRightsPage = "YourWiki:Copyright";
$wgRightsText = "copyright Roblox Network";
## The URL base path to the directory containing the wiki;
## defaults for all runtime URL paths are based off of this.
## For more information on customizing the URLs please see:
## http://www.mediawiki.org/wiki/Manual:Short_URL
$wgScriptPath = '/tra';
$wgRunJobsAsync = false;
$wgArticlePath = $wgScriptPath.'/$1';
$wgScriptExtension = ".php";
## The relative URL path to the skins directory
$wgStylePath = "$wgScriptPath/skins";
## The relative URL path to the logo. Make sure you change this from the default,
## or else you'll overwrite your logo when you upgrade!
#$wgLogo = "$wgStylePath/common/images/wiki.png";
$wgLogo = "$wgStylePath/common/images/wiki.png";
## UPO means: this is also a user preference option
$wgEnableEmail = true;
$wgEnableUserEmail = true; # UPO
$wgEmergencyContact = 'info#roblox-network.com';
$wgPasswordSender = 'no-reply#roblox-network.com';
$wgEnotifUserTalk = false; # UPO
$wgEnotifWatchlist = false; # UPO
$wgEmailAuthentication = true;
## Database settings
$wgDBtype = "mysql";
$wgDBserver = 'mysql';
$wgDBname = 'REMOVED';
$wgDBuser = 'REMOVED';
$wgDBpassword = 'REMOVED';
# MySQL specific settings
$wgDBprefix = 'REMOVED';
# MySQL table options to use during installation or update
$wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
# Experimental charset support for MySQL 4.1/5.0.
$wgDBmysql5 = false;
## Shared memory settings
$wgMainCacheType = CACHE_NONE;
$wgMemCachedServers = array();
## To enable image uploads, make sure the 'images' directory
## is writable, then set this to true:
$wgEnableUploads = true;
$wgUseImageMagick = true;
$wgImageMagickConvertCommand = "/usr/bin/convert";
# InstantCommons allows wiki to use images from http://commons.wikimedia.org
$wgUseInstantCommons = false;
## If you use ImageMagick (or any other shell command) on a
## Linux server, this will need to be set to the name of an
## available UTF-8 locale
$wgShellLocale = "en_US.utf8";
## If you want to use image uploads under safe mode,
## create the directories images/archive, images/thumb and
## images/temp, and make them all writable. Then uncomment
## this, if it's not already uncommented:
#$wgHashedUploadDirectory = false;
## If you have the appropriate support software installed
## you can enable inline LaTeX equations:
$wgUseTeX = false;
## Set $wgCacheDirectory to a writable directory on the web server
## to make your wiki go slightly faster. The directory should not
## be publically accessible from the web.
#$wgCacheDirectory = "$IP/cache";
# Site language code, should be one of ./languages/Language(.*).php
$wgLanguageCode = 'en';
$wgSecretKey = "REMOVED";
# Site upgrade key. Must be set to a string (default provided) to turn on the
# web installer while LocalSettings.php is in place
$wgUpgradeKey = "REMOVED";
## Default skin: you can change the default skin. Use the internal symbolic
## names, ie 'standard', 'nostalgia', 'cologneblue', 'monobook', 'vector':
$wgDefaultSkin = "vector";
## For attaching licensing metadata to pages, and displaying an
## appropriate copyright notice / icon. GNU Free Documentation
## License and Creative Commons licenses are supported so far.
#$wgEnableCreativeCommonsRdf = true;
$wgRightsPage = "Roblox Network"; # Set to the title of a wiki page that describes your license/copyright
$wgRightsUrl = "https://roblox-network.com/terms/";
$wgRightsText = "";
$wgRightsIcon = "";
# $wgRightsCode = ""; # Not yet used
# Path to the GNU diff3 utility. Used for conflict resolution.
$wgDiff3 = "/usr/bin/diff3";
# Query string length limit for ResourceLoader. You should only set this if
# your web server has a query string length limit (then set it to that limit),
# or if you have suhosin.get.max_value_length set in php.ini (then set it to
# that value)
$wgResourceLoaderMaxQueryLength = -1;
# End of automatically generated settings.
# Add more configuration options below.
require_once "$IP/skins/Vector/Vector.php";
# ------------------ Below is the custom settings. ----------------------------
require_once "$IP/extensions/ConfirmAccount/ConfirmAccount.php";
require_once "$IP/extensions/SecurePoll/SecurePoll.php";
$wgShowSQLErrors = true;
$wgDebugDumpSql = true;
$wgShowDBErrorBacktrace = true;
$wgConfirmAccountContact = 'tra#roblox-network.com';
# ------------------ Below is the custom permissions. ----------------------------
$wgGroupPermissions['sysop']['securepoll-create-poll'] = true;
# ------------------ Below is loginform permissions. ----------------------------
$wgAccountRequestMinWords = 10;
$wgConfirmAccountRequestFormItems['Biography']['enabled'] = false;
$wgMakeUserPageFromBio = false;
$wgAutoWelcomeNewUsers = false;
$wgConfirmAccountRequestFormItems = array(
'UserName' => array( 'enabled' => true ),
'RealName' => array( 'enabled' => false ),
'Biography' => array( 'enabled' => false, 'minWords' => 50 ),
'AreasOfInterest' => array( 'enabled' => false ),
'CV' => array( 'enabled' => false ),
'Notes' => array( 'enabled' => true ),
'Links' => array( 'enabled' => false ),
'TermsOfService' => array( 'enabled' => true ),
);
# ------------------ Below is loginform restricted. ----------------------------
function efLoginFormMessage( &$template ) {
$template->set( 'header', "(For an account to edit articles with, contact Mr.Master3395, info#roblox-network.com )");
return true;
}
$wgHooks['UserLoginForm'][]='efLoginFormMessage';
# End of automatically generated settings.
# Add more configuration options below.
require_once "$IP/skins/Vector/Vector.php";
# Disable reading by anonymous users
$wgGroupPermissions['*']['read'] = true;
# But allow them to access the login page or else there will be no way to log in!
# (You also might want to add access to "Main Page", "Help:Contents", etc.)
$wgWhitelistRead = array ("Special:Userlogin");
# Disable anonymous editing
$wgGroupPermissions['*']['edit'] = false;
# Prevent new user registrations except by sysops
$wgGroupPermissions['*']['createaccount'] = false;
# ------------------ Below is Group Permissions. ----------------------------
$wgGroupPermissions['*']['edit'] = false;
$wgGroupPermissions['user']['edit'] = false;
$wgGroupPermissions['sysop']['edit'] = true;
# ------------------ Below is Uploads. ----------------------------
$wgUploadPath = "$wgScriptPath/uploads"; ## Wiki 1.5 defaults to /images, but allows more than just images
$wgUploadDirectory = "$IP/uploads"; ## Wiki 1.5 defaults to /images, but allows more than just images
## To enable image uploads, make sure the above '$wgUploadPath' directory is writable by Apache User or group.
## ''(i.e. chmod og+w uploads images)'' then the following should be true:
$wgEnableUploads = true;
$wgUseImageMagick = true;
$wgImageMagickConvertCommand = "/usr/bin/convert";
## If you want to use image uploads under safe mode, create the directories images/archive, images/thumb and
## images/temp, and make them all writable. Then uncomment this, if it's not already uncommented:
$wgHashedUploadDirectory = false;
# ------------------ Below is Uploads. ----------------------------
// Add just one filetype to the default array
$wgFileExtensions[] = 'pdf';
// Add several file types to the default array
$wgFileExtensions = array_merge(
$wgFileExtensions, array(
'pdf', 'ppt', 'jp2', 'webp', 'doc','docx', 'xls', 'xlsx'
)
);
// Override the default with a bundle of filetypes:
$wgFileExtensions = array(
'png', 'gif', 'jpg', 'jpeg', 'jp2', 'webp', 'ppt', 'pdf', 'psd',
'mp3', 'xls', 'xlsx', 'swf', 'doc','docx', 'odt', 'odc', 'odp',
'odg', 'mpp'
);
$wgMimeDetectorCommand = "file -bi";
# ------------------ Below is Google Analytics. ----------------------------
require_once "$IP/extensions/googleAnalytics/googleAnalytics.php";
// Replace xxxxxxx-x with YOUR GoogleAnalytics UA number
$wgGoogleAnalyticsAccount = 'UA-37062089-17';
// Add HTML code for any additional web analytics (can be used alone or with $wgGoogleAnalyticsAccount)
$wgGoogleAnalyticsOtherCode = '<script type="text/javascript" src="https://analytics.example.com/tracking.js"></script>';
// Optional configuration (for defaults see googleAnalytics.php)
// Store full IP address in Google Universal Analytics (see https://support.google.com/analytics/answer/2763052?hl=en for details)
$wgGoogleAnalyticsAnonymizeIP = false;
// Array with NUMERIC namespace IDs where web analytics code should NOT be included.
$wgGoogleAnalyticsIgnoreNsIDs = array(500);
// Array with page names (see magic word Extension:Google Analytics Integration) where web analytics code should NOT be included.
$wgGoogleAnalyticsIgnorePages = array('ArticleX', 'Foo:Bar');
// Array with special pages where web analytics code should NOT be included.
$wgGoogleAnalyticsIgnoreSpecials = array( 'Userlogin', 'Userlogout', 'Preferences', 'ChangePassword', 'OATH');
// Use 'noanalytics' permission to exclude specific user groups from web analytics, e.g.
$wgGroupPermissions['sysop']['noanalytics'] = true;
$wgGroupPermissions['bot']['noanalytics'] = true;
// To exclude all logged in users give 'noanalytics' permission to 'user' group, i.e.
$wgGroupPermissions['user']['noanalytics'] = true;
# ------------------ Below is Logo. ----------------------------
$wgLogoHD = array(
"1.5x" => "images/main/wiki.png",
# "2x" => "images/main/wiki.png"
);
# The URL path of the shortcut icon.
# #since 1.6
#/
#$wgFavicon = 'images/main/favicon.ico';
# The URL path of the icon for iPhone and iPod Touch web app bookmarks.
# Defaults to no icon.
# #since 1.12
#/
$wgAppleTouchIcon = false;
# ------------------ Below is Login Link. ----------------------------
$wgHooks['PersonalUrls'][] = 'onPersonalUrls';
function onPersonalUrls( array &$personal_urls, Title $title, SkinTemplate $skin ) {
// Add a link to Special:RequestAccount if a link exists for login
if ( isset( $personal_urls['login'] ) || isset( $personal_urls['anonlogin'] ) ) {
$personal_urls['createaccount'] = array(
'text' => wfMessage( 'requestaccount' )->text(),
'href' => SpecialPage::getTitleFor( 'RequestAccount' )->getFullURL()
);
}
return true;
}
Looks like your issue is connected to Confirm Account extension only, because all other links seems to be fine. I've noticed that $wgServer is missing from your config, so try to specify $wgServer variable in LocalSettings.php like this:
$wgServer = "https://roblox-network.com";
https is default port 443 and port 80 is reserved for http.
The problem indicates that port 80 is not handling SSL.

Perl6: How to load a class module dynamically?

How can I load a OO-style module dynamically?
#!/usr/bin/env perl6
use v6;
my $r = prompt ':';
if $r {
require Text::CSV; # Error:
my $csv = Text::CSV.new; # Could not find symbol '&CSV'
} else {
require File::Temp <&tempfile>;
my ( $filename , $filehandle ) = tempfile; # this works
}
As explained in the perl6 doco here, you can dynamically load the module but;
To import symbols you must define them at compile time.
So, the code in the else clause works because of the explicit request to import <&tempfile>.
The closest thing to getting the code in the if clause to work that I can see is this (which is mostly taken from that earlier doco reference):
use v6.c ;
sub load-a-module($name) {
require ::($name) ;
my $instance = ::($name).new() ;
return $instance ;
}
my $module = "Text::CSV" ;
my $csv = load-a-module $module ;
say $csv.WHAT ;
# say $csv.^methods ; # if you really want to be convinced
# outputs: (CSV)

issue accessing lexical scope using B

For debugging purposes I'd like to Access the lexical scope of different subroutines with a specific Attribute set. That works fine. I get a Problem when the first variable stores a string, then I get a empty string. I do something like this:
$pad = $cv->PADLIST; # $cv is the coderef to the sub
#scatchpad = $pad->ARRAY; # getting the scratchpad
#varnames = $scratchpad[0]->ARRAY; # getting the variablenames
#varcontents = $scratchpad[1]->ARRAY; # getting the Content from the vars
for (0 .. $#varnames) {
eval {
my $name = $varnames[$_]->PV;
my $content;
# following line matches numbers, works so far
$content = $varcontent[$_]->IVX if (scalar($varcontent[$_]) =~ /PVIV=/);
# should match strings, but does give me undef
$content = B::perlstring($varcontent[$_]->PV) if (scalar($varcontent[$_]) =~ /PV=/);
print "DEBUGGER> Local variable: ", $name, " = ", $content, "\n";
}; # there are Special vars that throw a error, but i don't care about them
}
Like I said in the comment the eval is to prevent the Errors from the B::Special objects in the scratchpad.
Output:
Local variable: $test = 42
Local variable: $text = 0
The first Output is okay, the second should Output "TEXT" instead of 0.
What am I doing wrong?
EDIT: With a little bit of coding I got all values of the variables , but not stored in the same indexes of #varnames and #varcontents. So now is the question how (in which order) the values are stored in #varcontents.
use strict;
use warnings;
use B;
sub testsub {
my $testvar1 = 42;
my $testvar2 = 21;
my $testvar3 = "testval3";
print "printtest1";
my $testvar4 = "testval4";
print "printtest2";
return "returnval";
}
no warnings "uninitialized";
my $coderef = \&testsub;
my $cv = B::svref_2object ( $coderef );
my $pad = $cv->PADLIST; # get scratchpad object
my #scratchpad = $pad->ARRAY;
my #varnames = $scratchpad[0]->ARRAY; # get varnames out of scratchpad
my #varcontents = $scratchpad[1]->ARRAY; # get content array out of scratchpad
my #vars; # array to store variable names adn "undef" for special objects (print-values, return-values, etc.)
for (0 .. $#varnames) {
eval { push #vars, $varnames[$_]->PV; };
if ($#) { push #vars, "undef"; }
}
my #cont; # array to store the content of the variables and special objects
for (0 .. $#varcontents) {
eval { push #cont, $varcontents[$_]->IV; };
eval { push #cont, $varcontents[$_]->PV; };
}
print $vars[$_], "\t\t\t", $cont[$_], "\n" for (0 .. $#cont);
EDIT2: Added runnable script to demonstrate the issue: Variablenames and variablevalues are not stored in the same index of the two Arrays (#varnames and #varcontents).