views:

1044

answers:

1

Hey

I'm currently working on posting a file from a C# application to an image host (KalleLoad.net - with the owners consent, obviously).

I've gotten the actual posting of the request to work, but it's not returning what I expected. The owner of the upload site has provided me with an API (of sorts) which will return some XML with the URLs if I post the data to a certain address. I can successfully post the data and get a response from the server, however it is simply returning the code for the home page instead of the XML. I cannot understand why this is so.

I've also tried posting data to a simple PHP page on my local server and it too returns the code for the page, instead of what I instructed the page to return on post.

Below is the entirety of my current class for sending the data. I have also been comparing the headers I have been sending from my application with those that firefox is sending for the last half-hour and I can see no real game-changing differences between them (as far as I'm aware).

Any help on this would be fantastic and graciously received.

Regards, Andy Hunt

using System;
using System.Net;
using System.Text;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;

namespace Skimpt_3._0
{
    class PostFile
    {
        private Hashtable FormElements;
        private HttpWebRequest Request;
        private MemoryStream FileStream;

        private string CONTENT_BOUNDARY = "---------------------------265001916915724";

        public string ContentMIMEType;
        public string FormURL;
        public string FileName;
        public string Response;
        public string FileBoxName;

        //private int BufferSize;

        public PostFile(string Url, string strFileName)
        {
            FormElements = new Hashtable();
            FormURL = Url;
            Request = (HttpWebRequest)WebRequest.Create(Url);
            //BufferSize = 10240;
            FileStream = new MemoryStream();
            FileName = strFileName;
        }

        public void Send(Image image)
        {
            //Assign the request here too, just in case
            Request = (HttpWebRequest)WebRequest.Create(FormURL);

            Request.Method = "POST";
            Request.AllowWriteStreamBuffering = true;
            Request.ProtocolVersion = HttpVersion.Version11;
            Request.Headers.Add("Cache-Control", "no-cache");
            Request.KeepAlive = true;
            Request.ContentType = "multipart/form-data; boundary=---------------------------265001916915724";
            StartFileStream(FileStream);

            //Must be done in this order for stream to write properly:
            //----
            //Form elements
            //File header
            //Image
            //File trailer
            //----
            WriteStringToStream(FileStream, GetFormElements());
            WriteImageToStream(FileStream, image, FileName);

            CloseStream(FileStream);


            byte[] FileByteArray = FileStream.ToArray();

            Request.ContentLength = FileByteArray.Length;

            Stream PostingStream = Request.GetRequestStream();
            PostingStream.Write(FileByteArray, 0, FileByteArray.Length);

            WebResponse resp = (HttpWebResponse)Request.GetResponse();

            StreamReader SR = new StreamReader(resp.GetResponseStream());

            PostingStream.Close();

            FileStream.Close();
            Request.GetRequestStream().Close();
            Response = SR.ReadToEnd();
            Request = null;
        }

        private void CloseStream(MemoryStream FileStream)
        {
            byte[] BytesToWrite = Encoding.ASCII.GetBytes(CONTENT_BOUNDARY);
            FileStream.Write(BytesToWrite, 0, BytesToWrite.Length);
        }

        private void StartFileStream(MemoryStream FileStream)
        {
            // \r\n = new line
            string str = "POST " + FormURL +"Content-Type: multipart/form-data; boundary="+CONTENT_BOUNDARY+" \r\n \r\n" + CONTENT_BOUNDARY;
            byte[] BytesToWrite = Encoding.ASCII.GetBytes(str);
            FileStream.Write(BytesToWrite, 0, BytesToWrite.Length);
        }

        private Byte[] ConvertImageToByteArray(Image img)
        {
            //Method taken from http://www.csharp-station.com/Articles/Thumbnails.aspx and adapted
            MemoryStream memStream = new MemoryStream();
            img.Save(memStream, System.Drawing.Imaging.ImageFormat.Png);

            byte[] byteArray = new Byte[memStream.Length];

            memStream.Position = 0;
            memStream.Read(byteArray, 0, (int)memStream.Length);
            return byteArray;
        }

        public void AddFormElement(string ElementName, string ElementValue)
        {
            FormElements[ElementName] = ElementValue;
        }

        private string GetFormElements()
        {
            string str = "";
            IDictionaryEnumerator myEnumerator = FormElements.GetEnumerator();
            while (myEnumerator.MoveNext())
            {
                str += CONTENT_BOUNDARY + "\r\n" +
                    "Content-Disposition: form-data; name=" + myEnumerator.Key +
                    "\r\n\r\n" +
                    myEnumerator.Value +"\r\n";
            }
            return str;
        }


        private void WriteStringToStream(System.IO.MemoryStream stream, string String)
        {
            byte[] PostData = System.Text.Encoding.ASCII.GetBytes(String);
            stream.Write(PostData, 0, PostData.Length);
        }

        private void WriteImageToStream(System.IO.MemoryStream Stream, Image img, string FileName)
        {
            byte[] ByteArray = ConvertImageToByteArray(img);
            string head = CONTENT_BOUNDARY + "\r\n" +
                          "Content-Disposition: form-data; name=\"" + FileBoxName + "\"; filename=\"" + FileName + "\"\r\n" +
                          "Content-Type: " + ContentMIMEType + "\r\n\r\n";
            byte[] header = Encoding.ASCII.GetBytes(head);

            Stream.Write(header, 0, header.Length);

            Stream.Write(ByteArray, 0, ByteArray.Length);
        }
    }
}
+1  A: 

From what I can tell the POST request that you're submitting isn't formed correctly. I don't think the server understands how to interpret the data that you're sending (hence you're not getting the response you're expecting).

What a POST request to upload should look like

POST /_layouts/Upload.aspx HTTP/1.1
Content-Type: multipart/form-data; boundary=---------------------------7d9192265018e
Host: 127.0.0.1:25540
Content-Length: 3573
Connection: Keep-Alive
---------------------------7d9192265018e
-----------------------------7d9192265018e
Content-Disposition: form-data; name="uploadFile"; filename="C:\Temp\TestDocument.txt"
Content-Type: text/plain

blah
-----------------------------7d9192265018e--

(Note: the Content-Length value above is wrong: I removed some stuff for brevity.)


What a POST request that you're generating looks like

POST /WebSite1/Default.aspx HTTP/1.1
Cache-Control: no-cache
Content-Type: multipart/form-data; boundary=---------------------------265001916915724
Host: 127.0.0.1:25540
Content-Length: 8626
Expect: 100-continue
Connection: Keep-Alive

POST http://ipv4.fiddler:25540/WebSite1/Default.aspxContent-Type: multipart/form-data; boundary=-----------------------------265001916915724 

-----------------------------265001916915724-----------------------------265001916915724
Content-Disposition: form-data; name=""; filename="Test.png"
Content-Type: 

?PNG
-----------------------------265001916915724--

(Note: I've ommitted the PNG file data that I tested with)


By comparing the two, I can see the following problems:

  • The boundary is used incorrectly. You're using the same string in all places. After setting a boundary value (example: boundary=aaaBBBcccDDDeee) using the Content-Type header you use boundary prefixed with "--" (example: --boundary=aaaBBBcccDDDeee) to delimit each part of your multi-part form submission. The final boundary marks the end of the multi-part data and must be delimited with a "--" prefix and suffix (example: --boundary=aaaBBBcccDDDeee--. See RFC 1341 (MIME): 7 for more info.
  • There's a weird Expect: 100-continue header in your request. This looks like an issue with the .NET HttpWebRequest class and can be disabled. See HttpWebRequest and the Expect: 100-continue Header Problem for more.
  • The StartFileStream method is trying to write out a header value but you're putting it's output in the request body. It's also writing out headers incorrectly. I think this should be left out entirely (it looks like it adapted from some other upload code).
  • The field ContentMIMEType should be set to MIME type of the image you're uploading. It's not set at all so the Content-Type: of the image you're uploading is empty.
  • Ditto for the field FileBoxName.


How to debug this

You can use Fiddler to see the request that your sending. To set it up you'll need to be able to send a request to a web server. You should be able to run it and have it intercept any request you're trying to send.

Whew! That was long. Good luck and I hope that helps!

dariom
Thanks for the reply. I'll go look at all those things you suggested.As for the variables ContentMIMEType and FileBoxName, they are set outside the class. IE Instance.ContentMIMEType = "image/png";
That worked brilliantly, thanks. Turns out all I needed was to add the double dashes ("--") in front of the content boundaries every time. Once again, thank you so very much. It's been bugging me for days.
Ok, this works now, but only partially. For some reason it uploads, but only partially. It doesn't send the MIME type properly or the size or any of those properties. Any ideas?
Hi Andy. Sounds like you've made some progress! All I can suggest is going through the code with a debugger and using the Fiddler tool I linked to to see what sort of requests you're sending. You should be able to track down the missing values and fix your code accordingly. When I was looking at this, I couldn't help but think that there must be an easier way to do this (unfortunately not something I've worked much with though).
dariom
Thanks, dariom. I'll take a look at that Fiddler tool and see what's gone awry.
This is so very strange. I've compared the two sets of headers, added any missing ones and still it doesn't seem to want to post the mime type, size etc
Hi Andy. Some ideas: Are you 100% sure that Kalleload.net allow uploads without using their website? Try going to Kalleload.net and upload the same file you're trying to post. Capture the entire request in Fiddler. Do the same using your application and capture the request with Fiddler. Use a differencing tool like WinMerge to visually compare the two requests. Whitespace is probably significant.
dariom
I've checked it both with KalleLoad and a script on my local web server, and the headers and body are exactly the same in all 4 requests (2 to each location)
Oh, I also duplicated the IE request to KalleLoad via Fiddler (drag it in to the request builder) and it failed