package dedikit; # Perl API to DediKit.net dedicated server management panel # Copyright (C) 2006-2010 DediKit.net use strict; use LWP; use URI::Escape; use MIME::Base64; use JSON; my $API_URL = "https://node1.dedikit.net/api"; my $DB_ID; my $DB_LOGIN; my $DB_PASSWORD; my $ERROR; return 1; sub func { my %arg = @_; my $func = $arg{func}; my %args = %{ $arg{args} || {} }; my @args; push(@args, { name => "func", value => $func } ); map { push @args, { name => "args", value => $_ } } %args; my $ret = __http_request(page => 'func', params => \@args); # remove version part (DKDATA/JSON ) $ret =~s /^\nDKDATA\/JSON //; return from_json($ret); } sub __get_state { my ($content) = @_; my @content = split /[\n\r]+/, $content; shift @content; my $first_line = shift @content; if($first_line =~ /^STATE\t(.*$)/) { return $1; } else { return undef; } } sub __http_request { $dedikit::ERROR = ''; my (%arg) = @_; my $page = $arg{page}; my @params = (); if($arg{params}) { @params = @{$arg{params}}; } my $params = ''; for my $href (@params) { my $name = $href->{name}; my $value = $href->{value}; if(!defined $value) { $value = "DEDIKIT_API_UNDEF"; } my $value_q = uri_escape($value); $params .= "$name=$value_q&"; } my $uri = "$API_URL/$page.html?db_id=$DB_ID&db_login=$DB_LOGIN&db_password=$DB_PASSWORD&$params"; my $ua = LWP::UserAgent->new(timeout => 5); my $request = HTTP::Request->new(GET => $uri); my $response = $ua->simple_request($request); my $code = $response->code; my $content = $response->content; if($code ne '200') { $dedikit::ERROR = "__http_request returned wrong code ($code)"; return undef; } return $content; } sub __insert { $dedikit::ERROR = ''; my (%arg) = @_; my $table = $arg{table}; my %columns = %{$arg{columns}}; my @params; push(@params, { name => "table", value => $table } ); for my $column (keys %columns) { push(@params, { name => "columns", value => $column } ); push(@params, { name => "columns", value => $columns{$column} } ); } my $content = __http_request(page => "insert", params => \@params); if($dedikit::ERROR) { return undef; } my $state = __get_state($content); if(!$state) { $dedikit::ERROR = "Failed to get STATE after INSERT"; return undef; } if($state ne 'OK') { $dedikit::ERROR = $state; return undef; } return 1; } sub __update { $dedikit::ERROR = ''; my (%arg) = @_; my $table = $arg{table}; my $row_id = $arg{row_id}; my %columns = %{$arg{columns}}; my @params; push(@params, { name => "table", value => $table } ); push(@params, { name => "row_id", value => $row_id } ); for my $column (keys %columns) { push(@params, { name => "columns", value => $column } ); push(@params, { name => "columns", value => $columns{$column} } ); } my $content = __http_request(page => "update", params => \@params); if($dedikit::ERROR) { return undef; } my $state = __get_state($content); if(!$state) { $dedikit::ERROR = "Failed to get STATE after UPDATE"; return undef; } if($state ne 'OK') { $dedikit::ERROR = $state; return undef; } return 1; } sub __delete { my (%arg) = @_; my $table = $arg{table}; my $row_id = $arg{row_id}; my @params; push(@params, { name => "table", value => $table } ); push(@params, { name => "row_id", value => $row_id } ); my $content = __http_request(page => "delete", params => \@params); if($dedikit::ERROR) { return undef; } my $state = __get_state($content); if(!$state) { $dedikit::ERROR = "Failed to get STATE after DELETE"; return undef; } if($state ne 'OK') { $dedikit::ERROR = $state; return undef; } return 1; } sub __select { $dedikit::ERROR = ''; my (%arg) = @_; my $table = $arg{table}; my @columns = @{$arg{columns}}; my @params; push(@params, { name => 'table', value => $table } ); for my $column (@columns) { push(@params, { name => 'columns', value => $column } ); } my $content = __http_request(page => 'select', params => \@params); my $state = __get_state($content); if(!$state) { $dedikit::ERROR = "Failed to get STATE after SELECT"; return undef; } if($state ne 'OK') { $dedikit::ERROR = $state; return undef; } # start parsing selected fields my @SELECTED; for my $line (split /[\r\n]+/, $content) { if($line !~ /^SELECTED\t(.+$)/) { next; } my $fields = $1; my $idx = 0; my %row; for my $column_value (split /\t/, $fields) { my $column_name = $columns[$idx]; if(!$column_name) { $dedikit::ERROR = "Internal error, received more columns than requested"; return undef; } my $column_value_decoded = decode_base64($column_value); if($column_value_decoded eq 'DEDIKIT_API_UNDEF') { $column_value_decoded = undef; } $row{$column_name} = $column_value_decoded; $idx ++; } push(@SELECTED, \%row); } return @SELECTED; } sub __matched { my (%arg) = @_; my %row = %{$arg{row}}; if(!$arg{where} || scalar @{$arg{where}} == 0) { return 1; } my @where = @{$arg{where}}; my @result; my $waiting_logical = 0; for my $href (@where) { if($href->{op} eq 'EQ') { if($waiting_logical) { $dedikit::ERROR = "EQ while waiting AND|OR in WHERE clause"; return undef; } $waiting_logical = 1; my $result = 0; if($row{$href->{name}} eq $href->{value}) { $result = 1; } push(@result, { result => $result } ); } elsif($href->{op} eq 'NE') { if($waiting_logical) { $dedikit::ERROR = "NE while waiting AND|OR in WHERE clause"; return undef; } $waiting_logical = 1; my $result = 0; if($row{$href->{name}} ne $href->{value}) { $result = 1; } push(@result, { result => $result } ); } elsif($href->{op} eq 'REGEX') { if($waiting_logical) { $dedikit::ERROR = "REGEX while waiting AND|OR in WHERE clause"; return undef; } $waiting_logical = 1; my $result = 0; if($row{$href->{name}} =~ /$href->{value}/) { $result = 1; } push(@result, { result => $result } ); } elsif($href->{op} eq 'AND' || $href->{op} eq 'OR') { if(!$waiting_logical) { $dedikit::ERROR = "AND|OR while waiting EQ|NE in WHERE clause"; return undef; } push(@result, { op => $href->{op} } ); $waiting_logical = 0; } } if(!$waiting_logical) { $dedikit::ERROR = "AND|OR found at the end of WHERE clause"; return undef; } while(scalar @result > 1) { my $first = $result[0]->{result}; my $last = $result[2]->{result}; my $op = $result[1]->{op}; my $result = 0; if($op eq 'AND') { if($first && $last) { $result = 1; } } elsif($op eq 'OR') { if($first || $last) { $result = 1; } } shift @result; shift @result; shift @result; unshift(@result, { result => $result } ); } return $result[0]->{result}; } sub __fetch_columns_from_where { my (%arg) = @_; my @where = @{$arg{where}}; my @columns; for my $href (@where) { if($href->{op} eq 'EQ' || $href->{op} eq 'NE' || $href->{op} eq 'REGEX') { push(@columns, $href->{name}); } } return @columns; } # USER FUNCTIONS sub login { $dedikit::ERROR = ''; my (%arg) = @_; $DB_ID = $arg{id}; $DB_LOGIN = $arg{login} || 'admin'; $DB_PASSWORD = $arg{password}; if($arg{api_url}) { $API_URL = $arg{api_url}; } my $content = __http_request(page => 'ping'); if($dedikit::ERROR) { return undef; } my $state = __get_state($content); if(!$state) { $dedikit::ERROR = "Failed to get STATE after login"; return undef; } if($state ne 'OK') { $dedikit::ERROR = $state; return undef; } if($content !~ /PONG/) { $dedikit::ERROR = "Failed to login"; return undef; } return 1; } sub select { $dedikit::ERROR = ''; my (%arg) = @_; my $table = $arg{table}; if(!$arg{columns}) { $dedikit::ERROR = 'columns not specified'; return undef; } my @columns = @{$arg{columns}}; my @where; if($arg{where}) { @where = @{$arg{where}}; } # get data my %columns; for my $column (@columns) { $columns{$column} = 1; } my %columns_expanded = %columns; my @columns_from_where = __fetch_columns_from_where(where => \@where); for my $column (@columns_from_where) { $columns_expanded{$column} = 1; } $columns_expanded{id} = 1; my @columns_expanded; for my $column (keys %columns_expanded) { push(@columns_expanded, $column); } my @result = __select(table => $table, columns => \@columns_expanded); if($dedikit::ERROR) { return (); } my @filtered_result; for my $href (@result) { my $matched = __matched(row => $href, where => \@where); if(!defined $matched) { return (); } if($matched) { my %tmp; for my $key (keys %$href) { if($columns{$key}) { $tmp{$key} = $href->{$key}; } } push(@filtered_result, \%tmp); } } return @filtered_result; } sub delete { $dedikit::ERROR = ''; my (%arg) = @_; my $table = $arg{table}; my @where; if($arg{where}) { @where = @{$arg{where}}; } # get data my @columns = __fetch_columns_from_where(where => \@where); my @columns_with_id = @columns; push(@columns_with_id, 'id'); my @result = dedikit::__select(table => $table, columns => \@columns_with_id); if($dedikit::ERROR) { return undef; } for my $href (@result) { my $matched = __matched(row => $href, where => \@where); if(!defined $matched) { return (); } if($matched) { dedikit::__delete(table => $table, row_id => $href->{id}); if($dedikit::ERROR) { return undef; } } } } sub update { $dedikit::ERROR = ''; my (%arg) = @_; my $table = $arg{table}; my %columns = %{$arg{columns}}; my @where; if($arg{where}) { @where = @{$arg{where}}; } # get data my %columns_expanded = %columns; my @columns_from_where = __fetch_columns_from_where(where => \@where); for my $column (@columns_from_where) { $columns_expanded{$column} = 1; } $columns_expanded{id} = 1; my @columns_expanded; for my $column (keys %columns_expanded) { push(@columns_expanded, $column); } my @result = __select(table => $table, columns => \@columns_expanded); if($dedikit::ERROR) { return (); } for my $href (@result) { my $matched = __matched(row => $href, where => \@where); if(!defined $matched) { return (); } if($matched) { __update(table => $table, columns => \%columns, row_id => $href->{id}); } } return 1; } sub insert { my (%arg) = @_; return __insert(%arg); }