amelinda has asked for the wisdom of the Perl Monks concerning the following question:

The situation goes like this: So this is the code I have so far. But it doesn't seem to work. When I direct the output to a file, instead of the webserver, it appears to all be there. Just now, I tried to test it with open(OUT, "|telnet www 10941") instead of the other open(OUT...) (port 10941 is a fake webserver that just dumps any input to a file.), and it appears to be disconnecting before it finishes sending $mime. I'm at about my limit of being able to test/figure out what's wrong. Maybe I just can't make heads or tails out of the right MIME RFC's (which all assume you are trying to do email, and that you already know the HTTP headers you should be using instead (which I don't and can't seem to find documentation for)). Any help within my (above) context gratefully accepted.
#!/usr/bin/perl use MIME::Base64 (); # put some error checking here eventually $user = $ARGV[0]; $pass = $ARGV[1]; $file = $ARGV[2]; open(OUT,"|/usr/local/openssl/bin/openssl s_client -v -connect www:443 +") or die "Failure\n"; binmode OUT; $bound = "---------------------------2218759766176618872063491442"; # print out header stuff print OUT "POST /cgi-bin/save HTTP/1.0\n"; print OUT "Referer: https://www/upload.html\n"; print OUT "Connection: Keep-Alive\n"; print OUT "User-Agent: Amelindinator/0.1 [en] (amelinda\@mydomain.com) +\n"; print OUT "Host: www.mydomain.com:443\n"; print OUT "Content-type: multipart/form-data; boundary=$bound\n"; print OUT "Content-Length: "; # calculate content length open(FILE, "$file") or die "$!"; binmode FILE; # I'm not sure I need this. local($/) = undef; # slurp $mime = MIME::Base64::encode(<FILE>); close(FILE); $len = (length $mime) + (length $user) + (length $pass); $len += 514; # this is stuff that is constant # print rest of stuff. print OUT "$len\n\n"; print OUT "--$bound\n"; print OUT "Content-Disposition: form-data; name=\"user\"\n\n"; print OUT "$user\n"; print OUT "--$bound\n"; print OUT "Content-Disposition: form-data; name=\"pass\"\n\n"; print OUT "$pass\n"; print OUT "--$bound\n"; print OUT "Content-Disposition: form-data; name=\"FILE\"; filename=\"$ +file\"\n"; print OUT "Content-Transfer-Encoding: BASE64\n\n"; print OUT $mime; # print out the mimencoded bit here print OUT "--$bound\n"; print OUT "Content-Disposition: form-data; name=\"SUBMIT\"\n\n"; print OUT "Submit\n"; print OUT "--$bound--\n"; # close the stream, silly! close(OUT); exit;

Replies are listed 'Best First'.
(jcwren) RE: amelinda's problem
by jcwren (Prior) on Sep 30, 2000 at 04:37 UTC
    I realize you said that you can't get someone to install a lot of modules, but libwww-perl is completely self-contained, as nearly as I can tell. And within said bundle would be the perfect module you're looking for, HTML::Form. This will allow you to submit forms, takes care of all the nastiness of making sure the headers are right, was written by Gisle Aas, and is well tested. There's even an example of using it here on the site, Homenode Updater (shameless self plug).

    When I was playing with it earlier, I was using it to submit files, also. If you have any possibility of getting someone to install a module, this would be the way to go.

    --Chris

    e-mail jcwren
      I'm afraid I must differ with you on the self-containedness of libwww-perl. From the README:

      In order to install and use this package you will need Perl version 5.004 or better. Some modules within this package depend on other packages that are distributed separately from Perl. We recommend that you have the following packages installed before you install libwww-perl:

      URI
      MIME-Base64
      HTML-Parser
      libnet
      Digest::MD5

      While they say "reccommended," I was unable to install libwww-perl until I had the first three modules above.

      HTML::Form does seem to be a good place to look, though... I'm not sure how I missed it (lack of hierarchical information, perhaps?).

      amelinda

      Further, upon investigating HTML::Form, it does not know how to deal with type "file," only the various text types and radio/check types and the submit types. Unfortunately, that really limits it's applicability to my situation. Come to think about it, it probably limits the applicability of all of LWP/libwww-perl to my problem, certainly if they use HTML::Form to to the submission of forms.

      Has anyone else used LWP to submit a form with the "file" input type? Inquiring minds want to know.

      amelinda

(jcwren) RE: amelinda's problem (I am a hardheaded SOB)
by jcwren (Prior) on Oct 03, 2000 at 01:14 UTC
    And because I am a hardheaded S.O.B., I will still try to prove a point. Here's both the server and client side example of using HTML::Form to upload a file.

    Put the first file in your cgi-bin directory, and set it to be executable. A good name would be 'htmlformtest.pl'. Make sure you can get to it with your web browser. Put the second file in your user directory. Change the '$site' variable to point to the script in the cgi-bin directory. Run it. You should find a file in your local directory called 'testfile.text, and a file in /tmp, called 'uploaded.file'. These two files should be indentical in content. It seems to work here.

    Some of this code isn't pretty, doesn't do enough error checking, etc. It's demo code.
    #!/usr/local/bin/perl -w use strict; use CGI; use IO::File; use POSIX qw (tmpnam); my $finalname = '/tmp/uploaded.file'; { my $tmpnam; my $html = new CGI; print $html->header; if (!defined ($html->param ('uploadname')) || $html->param ('upload +name') eq '') { print while (<DATA>); print $html->end_html; exit; } my $fn = $html->param ('uploadname'); my $fh = $html->upload ('uploadname'); my $fd = ''; if (!defined ($fh)) { print "\$fh is not defined!\n"; print $html->end_html; exit; } while ((length ($fd) <= (64 * 1024)) && read ($fh, my $buffer, 1024 +)) { $fd .= $buffer; } if (length ($fd) == (64 * 1024)) { print "File too big!"; print $html->end_html; exit; } # # Get a temporary file name we know is ours. The loop resolves th +e race condition possibility. # for (;;) { $tmpnam = tmpnam (); sysopen (TMP, $tmpnam, O_RDWR | O_CREAT | O_EXCL) && last; } close (TMP); # # Create the temporary input file for the size-getter and thumbnai +l maker # if (open (SOURCE, ">$tmpnam")) { print SOURCE $fd; close SOURCE; } else { print "Couldn't open $tmpnam for writing: $!"; print $html->end_html; exit; } rename ($tmpnam, $finalname); print "File uploaded OK, saved as $finalname"; exit; } __DATA__ <HTML> <HEAD> <TITLE>File Upload Test</TITLE> </HEAD> <BODY> <FORM METHOD="POST" ENCTYPE="multipart/form-data"> <TABLE BORDER="0" ALIGN="CENTER"> <TR> <TD ALIGN="CENTER"> <P>Select file to upload: <INPUT TYPE="FILE" NAME="uploadn +ame"></P> </TD> </TR> <TR> <TD>&nbsp;</TD> </TR> <TR> <TD ALIGN="CENTER"> <INPUT TYPE="SUBMIT" NAME="sendfile" CHECKED="CHECKED" VAL +UE="Send me that file, Cowboy!"> </TD> </TR> </TABLE> </FORM> </BODY> </HTML>

    #!/usr/local/bin/perl -w use strict; use Carp; use LWP::UserAgent; use LWP::Simple; use HTML::Form; my $site = 'http://linux/release/cgi-bin/htmlformtest.pl'; my $testfile = './testfile.txt'; # # # { if (!-e $testfile) { open (FH, ">$testfile") || die $!; print FH "\nThis is a test file.\n"; print FH "It has been uploaded via HTML::Form\n"; print FH "And it has 5 lines in it.\n\n"; close FH; } my $req = get ($site); die "Eeek! Request failed.\n" if !$req; # # # my $form = HTML::Form->parse ($req, $site); if ($form->find_input ('uploadname')) { $form->value ('uploadname', [$testfile => [$testfile]]); my $userAgent = LWP::UserAgent->new; my $res = $userAgent->request ($form->click); die sprintf ("Eeek! Request failed (%s)\n", $res->status_line) u +nless ($res->is_success); print "\n", $res->content, "\n"; print "I seem to have uploaded the file OK!\n\n"; } else { die "Eeek! Whatever page we got, didn't have 'uploadname' as a +field!\n"; } }


    --Chris

    e-mail jcwren
      Thanks for being persistent! This is exactly the kind of thing I've been looking for as an example!

      I find it kind of interesting that you get around the thing I didn't see in HTML::Form by going and getting the form and then searching it for the right thing and filling it out blindly. I hadn't thought of doing that; it would be good errorchecking - make sure you can get to the site before submitting.

      For reference, here is some code I wrote when I was still trying to use libwww-perl (it didn't work, can you tell me why? (serious query)):

      #!/usr/bin/perl use HTTP::Request::Common; use LWP::UserAgent; use CGI qw(header -no_debug); my $URL = 'https://www.mydomain.com/upload.html'; my $req = POST $URL, Content_Type => 'form-data', Content => [ user => 'username', pass => 'password', FILE => ['./binaryfile'], # this file will be uploaded ]; my $res = LWP::UserAgent->new->request($req); print header, $res->is_success ? $res->content : $res->status_line;

      Yes, yes, Carp, strict, -w and all that. Consider it read, for conciseness' sake. Maybe I'll try it again using HTML::Form.

        Well, it passed the syntax check. Since I don't have a server that's configured to run https, I can't really test it. Perhaps you could describe the error in a little more detail.

        --Chris

        e-mail jcwren
Re: amelinda's HTTP/MIME/file upload/not-a-cgi-but-a-client/minimal-module/perl problem
by amelinda (Friar) on Oct 06, 2000 at 03:32 UTC
    Thank you everyone who helped me with this, especially jcwren and fastolfe.

    In the end, it turned out to be a combination of factors involving UNIX/Windows CRLF issues, MIME encoding issues, an important and missing piece of HTTP syntax, assumptions by the cgi-script recieving the form, and general weirdness.

    jcwren's working LWP code massively helped me decipher the issues involved (especially that damned missing Content-Length needed for the FILE parameter), to the point where I could get my non-LWP code working and look at the cgi-bin's output and go "WTF?" and figure out that it was expecting it to be mimencoded and on top of that, mimencoded in a Windows environment! But now the cgi's chop;chop;$_ = "$_\n"; is chomp;chomp;$_ = "$_\n"; instead and all is well with the world.

Re: amelinda's HTTP/MIME/file upload/not-a-cgi-but-a-client/minimal-module/perl problem
by Fastolfe (Vicar) on Sep 30, 2000 at 03:07 UTC
    Are you sure Content-length is just the length of each item of content? I was always under the impression that this specified the amount of data the server was going to have to read after you were done with your headers. If so, you have to include all of your additional MIME headers, boundaries, etc.

    I'm not 100% certain about this, but it is consistent with your symptoms, so I thought I'd mention it.

      Right. That's why the next line is $len += 514;. It accounts for those extra bits (since they remain constant throughout all invocations of the code).
        We've been going back and forth in the Chatterbox for a bit, and I know you're pretty sure that 514 number is accurate, but I still think it isn't. I modified your code to append everything after your HTTP headers to $data, and added these two lines:
        print "\$len=$len\n"; print "data length=" . length($data) . "\n";
        Now in theory, these should be the same, but they aren't:
        (fastolfe) eddie:~$ perl test user password test.file $len=555 data length=580
        Try adding 25 to your number and see if your problem goes away.. *shrug*.
        Oh.