[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Implementation of HTTP PUT request, Upload forms



------------------------------------------------------------------------------      
      ////// 3    // // ////// // // // ////// //////
      // //      ///// //  //  ///////   //   //  // 
     // //      // // //////   //  //   //   ////// 
------------------------------------------------------------------------------
Implementation of HTTP PUT request, Upload forms

Holger Zimmermann <zimpel@t-online.de>

V1.0                                                           22 June 1998

replaces parts of HOWTO document from 16/June/98





------------------------------------------------------------------------------
Contents
========

1.  PUT
1.1 CGI program
1.2 Configuration changes
1.3 Security
2.  form uploads
2.1 HTML upload form
2.2 CGI program
2.3 Security



1.  PUT
=======
The standard Pi3Web server configuration raises a 501 error if request methods
are other than GET or HEAD:

        CheckType Condition="&not(&or(&cmpi($m,GET),&cmpi($m,HEAD)))" \
                StatusCode StatusCode="501"

The task is to build a handler for the PUT request method and to redirect
PUT requests to this handler. It is possible to build this handler as a
plug in with C++ and calls to the Pi3API functions but I tried a CGI script
with Perl.



1.1 CGI program
---------------
#!/usr/bin/perl

# ---------------------------------------------------------
# Programm: put.pl
# Version:  0.1
# Synopsis: To save a file to handle PUT requests
#           PERL script for PI3Web server
# Author:   Holger Zimmermann
# ---------------------------------------------------------
# web:      http://home.t-online.de/home/zimpel@t-online.de
# mailto:   zimpel@t-online.de
# ---------------------------------------------------------
# calling conventions
#
# Pi3 handler:  DefaultCommandLine "perl c:\\pi3web\\cgi-bin\\put.pl %p%q"
#

use pi3lib;

if ((-e @ARGV[0]) && !(-w @ARGV[0])) {
   print "Content-type: text/plain\n\nPermission denied.";
}
else {
   Lock(@ARGV[0]);
   open(FILE, "+>@ARGV[0]") || die "Can't open!";
   binmode FILE;
   binmode STDIN;
   read(STDIN,$in,$ENV{CONTENT_LENGTH});
   print FILE $in || die "Can't write!";
   close(FILE) || die "Can't close!";
   Unlock(@ARGV[0]);
   print "Content-type: text/plain\n\nUpload success.";
};

The perl script is very simple. The stumble stone is not to forget
to switch the file handles into binary mode if you are under windows.
Otherwise the STDIN is only read until the first EOF char occurs and
the server resets the connection due to this incomplete read.
A preference is to give the name of the file in the first command line
line argument.
Note: You must have the author's pi3lib.pm Perl module to run the script.
Contact the author for it.



1.2 Configuration changes
-------------------------
The changes in the configuration are 2 new objects:
1.) A flexible handler object named Put
2.) A CGIClass object named PutCGI

Some changes were made in the mapping configuration to make PUT requests secure
with authentication and in the http dispatcher configuration to raise the
handler. The description of the in existence objects is abbreviated the PutCGI
object is 

<Object>
  Name Start
  Class FlexibleHandlerClass
  ...
  Mapping Condition="&cmpi($m,PUT)" \
    PathMapper From="/upload/" To="Upload\" \
    Action="&dbreplace(response,string,AuthenticationRealm,User)"
  Mapping Condition="&cmpi($m,PUT)" \
    PathMapper From="/cgi-bin/" To="Cgi-Bin\" \
    Action="&dbreplace(response,string,AuthenticationRealm,Administration)"
  Mapping Condition="&cmpi($m,PUT)" \
    PathMapper From="/icons/" To="Icons\" \
    Action="&dbreplace(response,string,AuthenticationRealm,Administration)"
  Mapping Condition="&cmpi($m,PUT)" \
    PathMapper From="/" To="Webroot\" \
    Action="&dbreplace(response,string,AuthenticationRealm,Administration)"
  ...
  # Default Mappings
</Object>

<Object>
  Name HTTPLogicObject
  Class HTTPDispatcherClass
  ...
  Handlers Start Scripts WinScripts FastCGIScripts ISAPI Put Default
  ...
</Object>

<Object>
  Name Put
  Class FlexibleHandlerClass
  CheckAuth Authenticate
  CheckAuth ReturnCode ReturnCode=COMPLETED
# No path checking 'cause also non existing files can be PUTted
  CheckPath ReturnCode ReturnCode=COMPLETED
# Here I could place an additional filter to allow
# only URL's beginning with allowed path
# CheckType Condition="&not(&regexp('/upload/*',$z))" \
#   StatusCode StatusCode="403"
  CheckType ReturnCode ReturnCode=COMPLETED
  CheckAccess ReturnCode ReturnCode="COMPLETED"
# No access checking since fail for non existing files
# CheckAccess AccessByFile RequirePermissions="W"
  Condition "&cmpi($m,PUT)"
  Handle PutCGI
</Object>

<Object>
  Name PutCGI
  Class CGIClass
  ...
  DefaultCommandLine "perl c:\\pi3web\\cgi-bin\\put.pl %p%q"
  ...
</Object>



1.3 Security
------------
The current problem is that in existence mappings are not filtered for
the request method. I corrected this very simple with adding a conditional
mapping if request method is PUT and an authentication for this case.
To be sure to allow uploads only in 1 directory remove comments from the
following lines from the Put object:

# CheckType Condition="&not(&regexp('/upload/*',$z))" \
#   StatusCode StatusCode="403"

A current problem is the access checking for write access rights. The
RefuseFileByMask and CheckAccess functions could not be used since
the upload of not existing files isn't allowed then. Besides this the
mechanisms of the operating system are working if the target file is
read only etc.



2.  form uploads
================
It is possible to create a HTML upload form and to send the form data in the
following multipart message form to the server:

-----------------------------2224084169055
Content-Disposition: form-data; name="upfile"; filename="c:\rfc\rfc2068.html"
Content-Type: text/html

.
.
.
-----------------------------2224084169055
Content-Disposition: form-data; name="note"

Annotation
-----------------------------2224084169055--

A CGI program has to take the data and to write it again in a correct file.
The CGI program I wrote handles supplementary the following tasks:
- write the destination file
- write a file with remarks to the upload (date, source, destination,
  remote host, annotation, errors)
- write the annotations into the .desc file for the Pi3Web directory indexes



2.1 HTML upload form
--------------------
<form method='POST' enctype='multipart/form-data' action='/cgi-bin/fupload.pl'>
File to upload: <input type=file name=upfile><br>
Notes about the file: <input type=text name=note><br>
<br>
<input type=submit value=Press> to upload the file!
</form>



2.2 CGI program
---------------
#!/usr/bin/perl

# ---------------------------------------------------------
# Programm: fupload.pl
# Version:  0.1
# Synopsis: To save a file to handle form data from
#           a upload form:
#             - save file
#             - write a .note file
#             - refresh the .desc file for Pi3Web directory
#               index (HTML table)
#           PERL script for PI3Web server
# Author:   Holger Zimmermann
# ---------------------------------------------------------
# web:      http://home.t-online.de/home/zimpel@t-online.de
# mailto:   zimpel@t-online.de
# ---------------------------------------------------------
# calling conventions
#
# form:     <action='/cgi-bin/fupload.pl'>
#

use pi3lib;

# a directory prefix to redirect uploads to a save directory 
$prefix=".\\";
# extension for the annotation file, if empty no annotation file is written
$noteext=".note";
# name of the description file for Pi3Web directory index
$desc=".desc";
$tmpfilename = sprintf("%8x",time).".TMP";
Lock($tmpfilename);
open(TMPFILE, ">$tmpfilename") || die "Can't open!";
binmode TMPFILE;
binmode STDIN;
# open(IN, "test.txt") || die "Can't open!";
# binmode IN;
my $id;
my $part1;
my $part2;
my $buf1;
my $buf2;
my $hdr1;
my $hdr2;
my $errcode = "Unknown Error";
my $errstatus = 0;

# while (<IN>) {
while (<STDIN>) {
   if ((/--/) && !$id) {
     $id = $_;
     $id =~ s/--*//;
   };
   if ($hdr1 =~ /\r\n\r\n/) {
      if (!$part2) {
         $part2 = $_ =~ /$id/;
         $buf1 = substr($buf1,0,length($buf1) - 2) if ($part2);
         print TMPFILE $buf1;
         $buf1 = $_;
      };
      if ($part2) {
         if ($hdr2 =~ /\r\n\r\n/) {
            $buf2 .= $_;
         }
         else {
            $hdr2 .= $_;
         };
      };
   }
   else {
      $hdr1 .= $_;
   };
};
# close(IN);
close(TMPFILE);
Unlock($tmpfilename);
if ($hdr1 =~ /Content-Disposition: form-data; name="upfile";/) {
   $filepath=substr($hdr1,index($hdr1,"filename=\"") + length("filename=\""),
      rindex($hdr1,"\"") - (index($hdr1,"filename=\"") +
length("filename=\"")));
   if (index($filepath,"\\") != -1) {
      $aname = substr($filepath, rindex($filepath,"\\") + 1, length($filepath));
   };
   if (index($filepath,"/") != -1) {
      $aname = substr($filepath, rindex($filepath,"/") + 1, length($filepath));
   };
   $filename = $prefix.$aname;
   if (!(-e $filename) || (-w $filename)) {
      rename $tmpfilename, $filename;
      print "Content-type: text/html\n\n";
      print "<H1>Upload success</H1>\n";
      print "<HR>\n";
      print "Thank you, your file $filepath has been stored.\n";
      $errcode = "Success";
   }
   else {
      &insuffrights;
   };
}
else {
   &invalidcontents;
};
if (($hdr2 =~ /Content-Disposition: form-data; name="note"/) && $noteext) {
   my ($fname, $ext) = split(/\./, $aname);
   $fname = $prefix.$fname."_".$ext.$noteext;
   Lock($fname);
   open(FILE, ">$fname") || die "Can't open!";     
   $buf2 =~ s/--*//g;
   $buf2 =~ s/$id//gi;
   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
   my $date = sprintf("%02.0f/%02.0f/%02.0f %02.0f\:%02.0f\:%02.0f
",$mon+1,$mday,$year,$hour,$min,$sec);
   print FILE "Source file : $filepath\n";
   print FILE "Destination : $filename\n";      
   print FILE "Error state : $errcode\n";
   print FILE "Uploaded at : $date\n";
   print FILE "Remote host : $ENV{REMOTE_HOST} <$ENV{REMOTE_ADDR}>\n";
   print FILE "Annotations : $buf2";
   close(FILE);
   Unlock($fname);
   if (!errstatus) {
      Lock($desc);
      Lock($tmpfilename);
      open(FILE, "+<$desc") || die "Can't open!";
      open(TMPFILE, ">$tmpfilename") || die "Can't open!";
      my $notthere = 1;
      $buf2 =~ s/\r\n//g;
      while (<FILE>) {
         if (!/$aname/) {
            print TMPFILE;
         }
         else {
            print TMPFILE $aname,"|",$buf2,"\n";
            $notthere = 0;
         };
      };
      print TMPFILE $aname,"|",$buf2,"\n" if ($notthere);
      close(TMPFILE);
      Unlock($tmpfilename);
      close(FILE);
      rename $tmpfilename, $desc;
      Unlock($desc);
   };
};
exit;

sub invalidcontents {
   unlink $tmpfilename;
   $errcode = "Invalid header";
   $errstatus = 1;
   print <<END;
Content-type: text/html

<H1>File upload error</H1>
<HR>
Invalid Content-Disposition.
Check your upload form!
END
  exit;
};

sub insuffrights {
   unlink $tmpfilename;
   $errcode = "Permission denied";
   $errstatus = 1;

   print <<END;
Content-type: text/html

<H1>File upload error</H1>
<HR>
Permission Denied.
Possibly you have no write access for the file $aname on the destination system.
END
  exit;
};

Note: You must have the author's pi3lib.pm Perl module to run the script.
Contact the author for it.



2.3 Security
------------
Since you can redirect the uploads to a own directory the security risk is
small. The .note files give the webmaster a history information of all
uploads.