summaryrefslogtreecommitdiff
path: root/src/StaticFarm/API.pm
diff options
context:
space:
mode:
Diffstat (limited to 'src/StaticFarm/API.pm')
-rw-r--r--src/StaticFarm/API.pm248
1 files changed, 248 insertions, 0 deletions
diff --git a/src/StaticFarm/API.pm b/src/StaticFarm/API.pm
new file mode 100644
index 0000000..50e50ca
--- /dev/null
+++ b/src/StaticFarm/API.pm
@@ -0,0 +1,248 @@
+package StaticFarm::API;
+
+use strict;
+use warnings;
+
+use Apache2::Const -compile => qw(:common);
+use Apache2::RequestIO ();
+use Apache2::RequestRec ();
+use Apache2::ServerUtil;
+
+use ExtUtils::Command;
+
+use File::Path qw(remove_tree);
+use JSON;
+use POSIX qw(strftime ctime);
+
+use constant IOBUFSIZE => 8192;
+
+my $CONTENT_DIR = $ENV{API_CONTENT_DIR};
+
+# ... now setup some serious stuff!!
+my $URI_PREFIX = '/-api';
+
+sub handler {
+ my $r = shift;
+ $r->content_type('application/json');
+
+ my $method = $r->method();
+
+ my $d = {
+ method => $method,
+ uri => $r->uri(),
+ args => $r->args(),
+ out => { message => "" },
+ };
+
+ ($d->{path}) = $r->uri() =~ /^$URI_PREFIX(.*)/;
+ $d->{fullpath} = "$CONTENT_DIR$d->{path}";
+
+ my %params = map {
+ s/\.\.//g;
+ my ($k, $v) = split '=', $_;
+ $v //= '';
+ $k => $v;
+ } split '&', $r->args();
+
+ $d->{params} = \%params;
+
+ if ($method eq 'GET') {
+ handler_get($r, $d);
+
+ } elsif ($method eq 'DELETE') {
+ handler_delete($r, $d);
+
+ } elsif ($method eq 'POST') {
+ handler_post($r, $d);
+
+ } elsif ($method eq 'PUT') {
+ handler_put($r, $d);
+
+ } else {
+ handler_unknown($r, $d);
+ }
+
+ return Apache2::Const::DONE;
+}
+
+sub data_out {
+ my ($r, $d, $status, $message) = @_;
+ my $p = $d->{params};
+
+ $d->{out}{message} = $message if defined $message;
+ $d->{out}{fullpath} = $d->{fullpath};
+
+ $status //= 200;
+ $d->{out}{status} = $status;
+ $r->status($status);
+
+ if (exists $p->{debug} and $p->{debug} == 1) {
+ for (grep !/^out$/, keys %$d) {
+ $d->{out}{debug}{$_} = $d->{$_};
+ }
+ }
+
+ print JSON->new->allow_nonref->pretty->encode($d->{out});
+}
+
+sub my_time {
+ return strftime("%Y-%m-%d %H:%M:%S", localtime(shift));
+}
+
+sub path_stat {
+ my $f = shift;
+
+ my @stat = stat($f);
+
+ my %data = (
+ size => -s $f,
+ hardlinks => $stat[3],
+ uid => $stat[4],
+ gid => $stat[5],
+ last_access => my_time($stat[8]),
+ last_modified => my_time($stat[9]),
+ last_status_change => my_time($stat[10]),
+ blocks => $stat[12],
+ ascii => (-T $f eq '' ? 0 : 1),
+ is_directory => (-d $f eq '' ? 0 : 1),
+ is_symlink => (-l $f eq '' ? 0 : 1),
+ is_file => (-f $f eq '' ? 0 : 1),
+ );
+
+ return \%data;
+}
+
+sub path_ls {
+ my $f = shift;
+
+ return [ map { s#.*/##; $_ } glob("$f/*") ];
+}
+
+sub path_exists {
+ my ($r, $d) = @_;
+
+ unless ( -e $d->{fullpath}) {
+ data_out($r, $d, 404, "No such file or directory: $d->{fullpath}");
+ return 0;
+ }
+
+ return 1;
+}
+
+sub path_writable {
+ my ($r, $d) = @_;
+
+ if (-e $d->{fullpath} && ! -w $d->{fullpath}) {
+ data_out($r, $d, 403, "Error: $d->{fullpath} permission denied.");
+ return 0;
+ }
+
+ return 1;
+}
+
+sub path_write {
+ my ($r, $d, $content) = @_;
+
+ return unless path_writable($r, $d);
+
+ if (defined $content) {
+ if ( -f $d->{fullpath} or ! -e $d->{fullpath} ) {
+ open my $fh, '>', $d->{fullpath} or do {
+ $d->{out}{message} = "Error: $d->{fullpath} $!";
+ data_out($r, $d, 500);
+ return;
+ };
+
+ print $fh $content;
+ close $fh;
+
+ $d->{out}{message} = "Wrote file successfully.";
+ } else {
+ data_out($r, $d, 500, "Can't put or post content like that. Destination may be a directory.");
+ return;
+ }
+
+ } else {
+ system("/usr/bin/touch \"$d->{fullpath}\"");
+ $d->{out}{message} = "Touched file or directory successfully.";
+ }
+
+ $d->{out}{stat} = path_stat($d->{fullpath});
+ data_out($r, $d);
+}
+
+
+sub handler_get {
+ my ($r, $d) = @_;
+ my $p = $d->{params};
+
+ return unless path_exists($r, $d);
+
+ $d->{out}{stat} = path_stat($d->{fullpath});
+ $d->{out}{content} = path_ls($d->{fullpath})
+ if -d $d->{fullpath} and exists $p->{ls} and $p->{ls} == 1;
+
+ data_out($r, $d, 200);
+}
+
+sub handler_delete {
+ my ($r, $d) = @_;
+ my $p = $d->{params};
+
+ return unless path_exists($r, $d);
+
+ $d->{out}{stat} = path_stat($d->{fullpath});
+
+ if ( -d $d->{fullpath} ) {
+ if (exists $p->{iamsure} and $p->{iamsure} == 1) {
+ my $err;
+ remove_tree($d->{fullpath}, { error => \$err });
+ unless (@$err) {
+ data_out($r, $d, 200, 'Directory deleted successfully.');
+ } else {
+ data_out($r, $d, 500, "Directory could not deleted completely. Run ls to see what's left over.");
+ }
+ } else {
+ data_out($r, $d, 403, 'Not removing directory recursively. If you want to do this set iamsure=1.');
+ }
+
+ } elsif ( -f $d->{fullpath} ) {
+ if (unlink($d->{fullpath})) {
+ data_out($r, $d, 200, 'File deleted successfully.');
+ } else {
+ data_out($r, $d, 500, "Error deleting: $d->{fullpath} $!.");
+ }
+ }
+}
+
+
+sub handler_post {
+ my ($r, $d) = @_;
+ my ($content, $buffer, $len) = ('', '', IOBUFSIZE);
+
+ $content .= $buffer while $r->read($buffer, $len);
+ path_write($r, $d, $content);
+}
+
+sub handler_put {
+ my ($r, $d) = @_;
+ my $p = $d->{params};
+
+ my $content = do {
+ if (exists $p->{content}) {
+ $p->{content};
+ } else {
+ undef;
+ }
+ };
+
+ path_write($r, $d, $content);
+}
+
+sub handler_unknown {
+ my ($r, $d) = @_;
+
+ data_out($r, $d, 501, "Method $d->{method} is not implemented.");
+}
+
+1;