MBH
MBH

Reputation: 16639

Uploading multipart file from android to WCF

I am trying to upload .JPG file from android to WCF webservice.

To upload the file from Android i tried 2 ways:

1- Retrofit:

        @Multipart
        @POST("/UploadFile/{fileName}")
        void UploadFile(@Path("fileName") String fileName, @Part("image") TypedFile image, Callback<String> callBack);

2- Android Asynchronous Http Client

here there was 2 options for uploading it:

a- Adding InputStream to the RequestParams:

        RequestParams params = new RequestParams();
        try {
            InputStream stream = new FileInputStream(fileImage);
            params.put("image", stream, fileImage.getName() );
            client.post(Constants.SERVICE_URL + "/UploadFile/" + fileImage.getName()
                            , params, getResponseHandler());
        } catch (Exception e) {
            Utils.LogError("ERROR: " + e.getLocalizedMessage());
        }

b- Ading File object to the RequestParams:

        RequestParams params = new RequestParams();
        try {
            //InputStream stream = new FileInputStream(fileImage);
            params.put("image", fileImage);
            client.post(Constants.SERVICE_URL + "/UploadFile/" + fileImage.getName()
                            , params, getResponseHandler());
        } catch (Exception e) {
            Utils.LogError("ERROR: " + e.getLocalizedMessage());
        }

All those, sent successfully to server, The received file before parsing looks something like this:

--b1b13fd2-4212-45bb-bb5c-fd4dc074fd1b
Content-Disposition: form-data; name="image"; filename="71d9d7fc-cfa8-40b6-b7aa-5c287cf31c72.jpg"
Content-Type: image/jpeg
Content-Length: 2906
Content-Transfer-Encoding: binary

���� JFIF      �� C .................very long string of this stuff
Þq�Ã�9�A?� �0pi1�zq�<�#��:��PV���]|�e�K�mv �ǜ.1�q���&��8��u�m�?�ӵ/���0=8�x�:t�8��>�ׁ���1�POM�k����eea1��ǧq�}8�6��q� � �� .;p1K�g�Onz�Q�oås�a�׌p1�?>3@���z��0=��m$�H ǧ��Ӄ�v?��x��<q��.8܃��� ��2}1�� c���ϧ q�oA�Rt>��t�=�?����2y�q�큊A����:��q�@���_�~�Q�w��Pu��Ƿ�q�#q��{cۦ���}0:b�|�=@��9�BEV���?O��װ�g���׎z<N� ��� v�=�?������=�<}x�#'�d�8��׌e����,�\�4wVV���f�pB���㢁�L{��%$�v裶G8x��b�?���� �]�=:�ӕ����
--b1b13fd2-4212-45bb-bb5c-fd4dc074fd1b--

So I used mulipart parser in order to take out the bytes of the file, the write them to file on the server to finish the uploading.

Here is the code of the multipartparser i used:

  public class MultipartParser
   {
public MultipartParser(string contents)
{
  this.Parse(contents);
}

private void Parse(string contents)
{
  Encoding encoding = Encoding.UTF8;
  this.Success = false;

  // Read the stream into a byte array
  byte[] data = encoding.GetBytes(contents);

  // Copy to a string for header parsing
  string content = contents;

  // The first line should contain the delimiter
  int delimiterEndIndex = content.IndexOf("\r\n");

  if (delimiterEndIndex > -1)
  {
    string delimiter = content.Substring(0, content.IndexOf("\r\n"));

    // Look for Content-Type
    Regex re = new Regex(@"(?<=Content\-Type:)(.*?)(?=\r\n)");
    Match contentTypeMatch = re.Match(content);

    // Look for filename
    re = new Regex(@"(?<=filename\=\"")(.*?)(?=\"")");
    Match filenameMatch = re.Match(content);

    #region added
    re = new Regex(@"(?<=Content\-Transfer\-Encoding:)(.*?)(?=\r\n\r\n)");
    Match contentTransferEncodingMatch = re.Match(content);
    #endregion

    // Did we find the required values?
    if (contentTypeMatch.Success && filenameMatch.Success && contentTransferEncodingMatch.Success)
    {
      // Set properties
      this.ContentType = contentTypeMatch.Value.Trim();
      this.Filename = filenameMatch.Value.Trim();
      this.ContentEncoding = contentTransferEncodingMatch.Value.Trim();


      // Get the start & end indexes of the file contents
      //int startIndex = contentTypeMatch.Index + contentTypeMatch.Length + "\r\n\r\n".Length;
      int startIndex = contentTransferEncodingMatch.Index + contentTransferEncodingMatch.Length + "\r\n\r\n".Length;

      byte[] delimiterBytes = encoding.GetBytes("\r\n" + delimiter);
      string finalDelimeterStr = "\r\n"+delimiter + "--";
      byte[] endDilimeterBytes = encoding.GetBytes(finalDelimeterStr);
      //byte[] fileBytes = Array.Copy()
      //int endIndex = IndexOf(data, endDilimeterBytes, startIndex);
      int endIndex = SimpleBoyerMooreSearch(data, endDilimeterBytes);

      int contentLength = endIndex - startIndex;

      // Extract the file contents from the byte array
      byte[] fileData = new byte[contentLength];

      Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength);

      this.FileContents = fileData;
      this.Success = true;
    }
  }
}

public int SimpleBoyerMooreSearch(byte[] haystack, byte[] needle)
{
  int[] lookup = new int[256];
  for (int i = 0; i < lookup.Length; i++) { lookup[i] = needle.Length; }

  for (int i = 0; i < needle.Length; i++)
  {
    lookup[needle[i]] = needle.Length - i - 1;
  }

  int index = needle.Length - 1;
  byte lastByte = needle.Last();
  while (index < haystack.Length)
  {
    var checkByte = haystack[index];
    if (haystack[index] == lastByte)
    {
      bool found = true;
      for (int j = needle.Length - 2; j >= 0; j--)
      {
        if (haystack[index - needle.Length + j + 1] != needle[j])
        {
          found = false;
          break;
        }
      }

      if (found)
        return index - needle.Length + 1;
      else
        index++;
    }
    else
    {
      index += lookup[checkByte];
    }
  }
  return -1;
}

public static byte[] ToByteArray(Stream stream)
{
  byte[] buffer = new byte[32768];
  using (MemoryStream ms = new MemoryStream())
  {
    while (true)
    {
      int read = stream.Read(buffer, 0, buffer.Length);
      if (read <= 0)
        return ms.ToArray();
      ms.Write(buffer, 0, read);
    }
  }
}

public bool Success
{
  get;
  private set;
}

public string ContentType
{
  get;
  private set;
}
public string ContentEncoding
{
  get;
  private set;
}


public string Filename
{
  get;
  private set;
}

public byte[] FileContents
{
  get;
  private set;
}

}

The parser is taking out the bytes, and parse the received multipart file.

The result file is not showing and it shows error reading file or something.

What i noticed after comparing the files it that the original and received file are different, here is the comparison in Notepad++:

Orig Rec comp

some letters are exists in the original and not exists in the received!

here is the WCF Function declaration and code:

IService.cs:

[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/UploadFile/{fileName}"
  , ResponseFormat = WebMessageFormat.Json)]
string UploadFile(string fileName ,Stream image);

Service.cs:

public string UploadFile(string fileName, Stream image)
{
  string dirPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Logs/");
  //string path = dirPath+"log.txt";

  // Read the stream into a byte array
  byte[] data = MultipartParser.ToByteArray(image);

  // Copy to a string
  string content = Encoding.UTF8.GetString(data);

  File.WriteAllText(dirPath + fileName + ".txt", content); // for checking the result file

  MultipartParser parser = new MultipartParser(content);

  if (parser != null )
  {
    if (parser.Success)
    {

      if (parser.FileContents == null)
        return "fail: Null Content";

      byte[] bitmap = parser.FileContents;

      File.WriteAllBytes(dirPath + fileName +"contents",bitmap);

      try
      {
        using (Image outImage = Image.FromStream(new MemoryStream(bitmap)))
        {
          outImage.Save(fileName, ImageFormat.Jpeg);
        }
        return "success";
      }
      catch (Exception e)
      { // I get this exception all the time
        return "Fail: e " + e.Message;
      }
    }
    return "fail not success";
  }

  return "fail";
}

I tried every possible solution came to my mind, still could not get whats wrong!!! is the problem in the encoding while sending or the parser!??

Please what can be the problem!? i am struggling with this for 3 days!

Thank you ALL :)

Upvotes: 1

Views: 1410

Answers (2)

MBH
MBH

Reputation: 16639

The were 2 problems:

  1. The parser
  2. Decoding the bytes to string was not good idea.

I modified the parser to this one and it will take care of the rest:

public class MultipartParser


{
    public MultipartParser(Stream stream)
{
  this.Parse(stream);
}

private void Parse(Stream stream)
{
  this.Success = false;
  if(!stream.CanRead)
    return;

  // Read the stream into a byte array
  byte[] data = MultipartParser.ToByteArray(stream);
  if (data.Length < 1)
    return;

  // finding the delimiter (the string in the beginning and end of the file
  int delimeterIndex = MultipartParser.SimpleBoyerMooreSearch(data, Encoding.UTF8.GetBytes("\r\n")); // here we got delimeter index
  if (delimeterIndex == -1) return;
  byte[] delimeterBytes = new byte[delimeterIndex];
  Array.Copy(data, delimeterBytes, delimeterIndex);

  // removing the very first couple of lines, till we get the beginning of the JPG file
  byte[] newLineBytes = Encoding.UTF8.GetBytes("\r\n\r\n");
  int startIndex = 0;

  startIndex = MultipartParser.SimpleBoyerMooreSearch(data, newLineBytes);
  if (startIndex == -1)
    return;

  int startIndexWith2Lines = startIndex + 4; // 4 is the bytes of "\r\n\r\n"
  int newLength = data.Length - startIndexWith2Lines;
  byte[] newByteArray = new byte[newLength];

  Array.Copy(data, startIndex + 4, newByteArray, 0, newLength - 1);

  // check for the end of the stream, is ther same delimeter
  int isThereDelimeterInTheEnd = MultipartParser.SimpleBoyerMooreSearch(newByteArray, delimeterBytes);
  if (isThereDelimeterInTheEnd == -1) return; // the file corrupted so

  int endIndex = isThereDelimeterInTheEnd - delimeterBytes.Length;

  byte[] lastArray = new byte[endIndex];

  Array.Copy(newByteArray, 0, lastArray, 0, endIndex);

  this.FileContents = lastArray;
  this.Success = true;
}
static byte[] GetBytes(string str)
{
  byte[] bytes = new byte[str.Length * sizeof(char)];
  System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
  return bytes;
}

static string GetString(byte[] bytes)
{
  char[] chars = new char[bytes.Length / sizeof(char)];
  System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
  return new string(chars);
}


public static int SimpleBoyerMooreSearch(byte[] haystack, byte[] needle)
{
  int[] lookup = new int[256];
  for (int i = 0; i < lookup.Length; i++) { lookup[i] = needle.Length; }

  for (int i = 0; i < needle.Length; i++)
  {
    lookup[needle[i]] = needle.Length - i - 1;
  }

  int index = needle.Length - 1;
  byte lastByte = needle.Last();
  while (index < haystack.Length)
  {
    var checkByte = haystack[index];
    if (haystack[index] == lastByte)
    {
      bool found = true;
      for (int j = needle.Length - 2; j >= 0; j--)
      {
        if (haystack[index - needle.Length + j + 1] != needle[j])
        {
          found = false;
          break;
        }
      }

      if (found)
        return index - needle.Length + 1;
      else
        index++;
    }
    else
    {
      index += lookup[checkByte];
    }
  }
  return -1;
}

private int IndexOf(byte[] searchWithin, byte[] serachFor, int startIndex)
{
  int index = 0;
  int startPos = Array.IndexOf(searchWithin, serachFor[0], startIndex);

  if (startPos != -1)
  {
    while ((startPos + index) < searchWithin.Length)
    {
      if (searchWithin[startPos + index] == serachFor[index])
      {
        index++;
        if (index == serachFor.Length)
        {
          return startPos;
        }
      }
      else
      {
        startPos = Array.IndexOf<byte>(searchWithin, serachFor[0], startPos + index);
        if (startPos == -1)
        {
          return -1;
        }
        index = 0;
      }
    }
  }

  return -1;
}

public static byte[] ToByteArray(Stream stream)
{
  byte[] buffer = new byte[32768];
  using (MemoryStream ms = new MemoryStream())
  {
    while (true)
    {
      int read = stream.Read(buffer, 0, buffer.Length);
      if (read <= 0)
        return ms.ToArray();
      ms.Write(buffer, 0, read);
    }
  }
}

public bool Success
{
  get;
  private set;
}

public byte[] FileContents
{
  get;
  private set;
}


}

So you can use this parser for this kind of multipart files encoding:

--b1b13fd2-4212-45bb-bb5c-fd4dc074fd1b
Content-Disposition: form-data; name="image"; filename="71d9d7fc-cfa8-40b6-b7aa-5c287cf31c72.jpg"
Content-Type: image/jpeg
Content-Length: 2906
Content-Transfer-Encoding: binary

���� JFIF      �� C .................very long string of this stuff
Þq�Ã�9�A?� �0pi1�zq�<�#��:��PV���]|�e�K�mv �ǜ.1�q���&��8��u�m�?�ӵ/��Ƿ�q�#q��{cۦ���}0:b�|�=@��9�BEV���?O��װ�g���׎z<N� ��� v�=�?������=�<}x�#'�d�8��׌e����,�\�4wVV���f�pB���㢁�L{��%$�v裶G8x��b�?���� �]�=:�ӕ����
--b1b13fd2-4212-45bb-bb5c-fd4dc074fd1b--

Hope it helps somebody else.

Upvotes: 1

JuergenKie
JuergenKie

Reputation: 109

You could try to encode the jpeg to base64 before sending it. As far as I know, this is a proper solution. Decoding it on the server, should be no problem. (Sry, I wanted to write a comment - but I'm not allowed to it)

Upvotes: 0

Related Questions