#!/usr/bin/perl # # p4xml.pl - a CGI script for serving up Perforce related data as # XML to web applications # # Accepted parameters: # cmd= - Perforce command (required) # path= - a depot path # rev= - a Perforce revision specifier # user= - a Perforce user # client= - a Perforce client # max= - max number of elements # followIntegs= - shockingly enough, follow integs a.k.a. '-i' # long= - fetch long descriptions # expr= - job query # showDetails= - show all info from a command, a la monitor # # Returned data: Perforce tagged data formatted as XML, root tag named # # Sample query: http://public.perforce.com/cgi-bin/p4xml.pl?cmd=changes;path=//guest/matt_attaway/p4xml/... # use strict; use Switch; my $user = "matt"; my $port = "public.perforce.com:1666"; my $p4exec = "/usr/local/bin/p4"; my $p4 = "$p4exec -ztag -p $port -u $user "; # let's get this party started; parse the query string my %vars = &getCgiVars; my $ results; if( ($ENV{'REQUEST_METHOD'} eq 'GET') ) { # das headers print "Content-type: text/xml\n\n" ; print ""; switch( $vars{"cmd"} ) { case "branches" { $results = &fetchBranches } case "changes" { $results = &fetchChanges } case "clients" { $results = &fetchClients } case "depots" { $results = &fetchDepots } case "jobs" { $results = &fetchJobs } case "labels" { $results = &fetchLabels } case "users" { $results = &fetchUsers } case "monitor" { $results = &fetchProcesses } else { $results = "Give me a command fool"; } } # the good stuff print "$results"; } sub fetchBranches { my( @branches, $cmdline ); # build the command $cmdline .= $p4 . "branches "; if( exists $vars{"user"} ) { if( !IsValidAlphaNumeric( $vars{"user"} ) ) { return "Alphanumeric values only for variable \"user\""; } $cmdline .= "-u " . $vars{"user"} . " "; } if( exists $vars{"max"} ) { if( !IsValidNumeric( $vars{"max"} ) ) { return "Numeric values only for variable \"max\""; } $cmdline .= "-m " . $vars{"max"} . " "; } # fetch the branches @branches = `$cmdline`; return &parsePerforceLists( "branch", "branch", @branches ); } sub fetchChanges { my( @changes, $cmdline ); # build the command $cmdline .= $p4 . "changes "; if( exists $vars{"user"} ) { if( !IsValidAlphaNumeric( $vars{"user"} ) ) { return "Alphanumeric values only for variable \"user\""; } $cmdline .= "-u " . $vars{"user"} . " "; } if( exists $vars{"client"} ) { if( !IsValidData( $vars{"client"} ) ) { return "Alphanumeric values only for variable \"client\""; } $cmdline .= "-c " . $vars{"client"} . " "; } if( exists $vars{"max"} ) { if( !IsValidNumeric( $vars{"max"} ) ) { return "Numeric values only for variable \"max\"" } $cmdline .= "-m " . $vars{"max"} . " "; } if( exists $vars{"followIntegs"} ) { $cmdline .= "-i "; } if( exists $vars{"long"} ) { $cmdline .= "-l "; } if( exists $vars{"path"} ) { if( !IsValidData( $vars{"path"} ) ) { return "Alphanumeric values only for variable \"path\""; } $cmdline .= $vars{"path"}; } if( exists $vars{"rev"} ) { if( !IsValidRevData( $vars{"rev"} ) ) { return "Numeric values only for variable \"rev\"" } $cmdline .= $vars{"rev"}; } # fetch the changes @changes = `$cmdline`; return &parsePerforceLists( "change", "change", @changes ); } sub fetchClients { my( @clients, $cmdline ); # build the command $cmdline .= $p4 . "clients "; if( exists $vars{"user"} ) { if( !IsValidAlphaNumeric( $vars{"user"} ) ) { return "Alphanumeric values only for variable \"user\""; } $cmdline .= "-u " . $vars{"user"} . " "; } if( exists $vars{"max"} ) { if( !IsValidNumeric( $vars{"max"} ) ) { return "Numeric values only for variable \"max\"" } $cmdline .= "-m " . $vars{"max"} . " "; } # fetch the clients @clients = `$cmdline`; return &parsePerforceLists( "client", "client", @clients ); } sub fetchDepots { my( @depots, $cmdline ); # build the command $cmdline .= $p4 . "depots "; # fetch the clients @depots = `$cmdline`; return &parsePerforceLists( "name", "depot", @depots ); } sub fetchJobs { my( @jobs, $cmdline ); # build the command $cmdline .= $p4 . "jobs "; if( exists $vars{"max"} ) { if( !IsValidNumeric( $vars{"max"} ) ) { return "Numeric values only for variable \"max\"" } $cmdline .= "-m " . $vars{"max"} . " "; } if( exists $vars{"followIntegs"} ) { $cmdline .= "-i "; } if( exists $vars{"long"} ) { $cmdline .= "-l "; } if( exists $vars{"expr"} ) { if( !IsValidData( $vars{"expr"} ) ) { return "Alphanumeric values only for variable \"expr\""; } $cmdline .= "-e " . '"' . $vars{"expr"} . '"' . " "; } # fetch the jobss @jobs = `$cmdline`; return &parsePerforceLists( "Job", "job", @jobs ); } sub fetchLabels { my( @labels, $cmdline ); # build the command $cmdline .= $p4 . "labels "; if( exists $vars{"user"} ) { if( !IsValidAlphaNumeric( $vars{"user"} ) ) { return "Alphanumeric values only for variable \"user\""; } $cmdline .= "-u " . $vars{"user"} . " "; } if( exists $vars{"max"} ) { if( !IsValidNumeric( $vars{"max"} ) ) { return "Numeric values only for variable \"max\"" } $cmdline .= "-m " . $vars{"max"} . " "; } if( exists $vars{"path"} ) { if( !IsValidData( $vars{"path"} ) ) { return "Alphanumeric values only for variable \"path\""; } $cmdline .= $vars{"path"}; } if( exists $vars{"rev"} ) { if( !IsValidRevData( $vars{"rev"} ) ) { return "Numeric values only for variable \"rev\"" } $cmdline .= $vars{"rev"}; } # fetch the labels @labels = `$cmdline`; return &parsePerforceLists( "label", "label", @labels ); } sub fetchProcesses { my( @processes, $cmdline ); # build command $cmdline .= $p4 . "monitor show "; if( exists $vars{"showDetails"} ) { $cmdline .= "-ael"; } # fetch the processes @processes = `$cmdline`; return &parsePerforceLists( "id", "process", @processes ); } sub fetchUsers { my( @users, $cmdline ); # build the command $cmdline .= $p4 . "users "; if( exists $vars{"max"} ) { if( !IsValidNumeric( $vars{"max"} ) ) { return "Numeric values only for variable \"max\"" } $cmdline .= "-m " . $vars{"max"} . " "; } if( exists $vars{"user"} ) { if( !IsValidAlphaNumeric( $vars{"user"} ) ) { return "Alphanumeric values only for variable \"user\""; } $cmdline .= $vars{"user"}; } # fetch the users @users = `$cmdline`; return &parsePerforceLists( "User", "user", @users ); } sub parsePerforceLists() { my $specKey = shift; my $tagName = shift; my @results = @_; my $output; my $currentElem; my $currentElemText; my $parsingElem = 0; my $needEndTag = 0; while( scalar(@results) ) { $_ = shift @results; # handle multiline fields if( $parsingElem ) { if( /^\.\.\. .*/ ) { unshift @results, $_; # check to see if this is the next tag is a "key" tag, and # if so chop the two newlines perforce likes to attach if( /^\.\.\. $specKey/ ) { chop $currentElemText;chop $currentElemText; } $output .= "$currentElemText"; $currentElemText = $currentElem = ""; $parsingElem = 0; } else { $currentElemText .= &escapeCDATA( $_ ); } next; } # toss away blank lines between sets of jobs next if /^$/; # throw on the end tag if we need it, and cut off the newline that p4 likes to append if( /^\.\.\. $specKey/ && $needEndTag ) { $output .= ""; } # handle actual output switch( $_ ) { case /^\.\.\. $specKey/ { /^\.\.\. $specKey (.*)/; $output .= "<$tagName id=\"$1\">"; } else { /^\.\.\. (\w*) (.*)/; $currentElem = $1; # escape XML unfriendly content $currentElemText = "<$currentElem>" . &escapeCDATA( $2 ); $parsingElem = 1; } } $needEndTag = 1; } chop $currentElemText; chop $currentElemText; if( $needEndTag ) { $output .= "$currentElemText"; } return $output; } sub escapeCDATA() { my $cdata = shift; $cdata =~ s/\&/&/g; $cdata =~ s//>/g; return $cdata; } sub getCgiVars( ) { my( $in, $key, $val ); # read entire string of CGI vars if( ($ENV{'REQUEST_METHOD'} eq 'GET') || ($ENV{'REQUEST_METHOD'} eq 'HEAD') ) { $in = $ENV{'QUERY_STRING'}; } elsif ($ENV{'REQUEST_METHOD'} eq 'POST') { read(STDIN, $in, $ENV{'CONTENT_LENGTH'}) ; } else { exit; } # resolve name/value pairs into %in foreach( split( /[&;]/, $in ) ) { s/\+/ /g ; my ($key, $val) = split /=/; $key =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/ge ; $val =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/ge ; $vars{$key}.= $val ; } return %vars ; } sub IsValidRevData() { my $value = shift; if( $value !~ /^[0-9\w\@\#\-]+$/ ) { return 0; } return 1; } sub IsValidData() { my $value = shift; if( $value !~ /^[0-9\w-\/]+$/ ) { return 0; } return 1; } sub IsValidAlphaNumeric() { my $value = shift; if( $value !~ /^[0-9\w]+$/ ) { return 0; } return 1; } sub IsValidNumeric() { my $value = shift; if( $value !~ /^[0-9]+$/ ) { return 0; } return 1; } sub IsValidAlpha() { my $value = shift; if( $value !~ /^[\w]+$/ ) { return 0; } return 1; } sub ReturnError() { }