Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parse multipart response for image download in ios

In my application, I'm downloading image from server as multipart content. In my response data I'm getting 2 parts: one is json content and other is downloaded file. The response is in following format.

--poa89012-3212-1232-9201-fdsakjkj921
Content-Type: application/json; charset=utf-8
Content-Disposition: inline; name=info

{
  //json content
}

--poa89012-3212-1232-9201-fdsakjkj921
Content-Disposition: file; name=file; filename=photo.png
Content-Type: application/octet-stream

// File data
˘íë77íí77Í¥2008:02:11 11:32:512008:02:1
------

I'm not able to handle this response which has 2 parts, when I tried to get the headers in didReceiveResponse: it gives the headers for the entire response whose content-type is multipart/mixed.Please show me the way to handle this response by splitting the json content and the file content.

like image 728
smily Avatar asked Mar 20 '23 10:03

smily


2 Answers

I also did have problems with http-multipart response. I wrote a category for NSData. Code below:

NSData+MultipartResponses.h

#import <Foundation/Foundation.h>

@interface NSData (MultipartResponses)

- (NSArray *)multipartArray;
- (NSDictionary *)multipartDictionary;

@end

NSData+MultipartResponses.m

#import "NSData+MultipartResponses.h"

@implementation NSData (MultipartResponses)

static NSMutableDictionary *parseHeaders(const char *headers)
{
   NSMutableDictionary *dict=[NSMutableDictionary dictionary];
   int max=strlen(headers);
   int start=0;
   int cursor=0;
   while(cursor<max)
   {
      while((headers[cursor]!=':')&&(headers[cursor]!='='))
      {
         cursor++;
      }
      NSString *key=[[NSString alloc] initWithBytes:(headers+start) length:(cursor-start) encoding:NSASCIIStringEncoding];
      cursor++;

      while(headers[cursor]==' ')
      {
         cursor++;
      }
      start=cursor;
      while(headers[cursor]&&(headers[cursor]!=';')&&((headers[cursor]!=13)||(headers[cursor+1]!=10)))
      {
         cursor++;
      }

      NSString *value;
      if((headers[start]=='"')&&(headers[cursor-1]=='"'))
      {
         value=[[NSString alloc] initWithBytes:(headers+start+1) length:(cursor-start-2) encoding:NSASCIIStringEncoding];
      }
      else
      {
         value=[[NSString alloc] initWithBytes:(headers+start) length:(cursor-start) encoding:NSASCIIStringEncoding];
      }
      [dict setObject:value forKey:key];

      if(headers[cursor]==';')
      {
         cursor++;
      }
      else
      {
         cursor+=2;
      }

      while(headers[cursor]==' ')
      {
         cursor++;
      }
      start=cursor;
   }
   return dict;
}

- (NSDictionary *)multipartDictionaryWithBoundary:(NSString *)boundary
{
   NSMutableDictionary *dict=[NSMutableDictionary dictionary];

   const char *bytes=(const char *)[self bytes];
   const char *pattern=[boundary cStringUsingEncoding:NSUTF8StringEncoding];

   int cursor=0;
   int start=0;
   int max=[self length];
   int keyNo=0;
   while(cursor<max)
   {
      if(bytes[cursor]==pattern[0])
      {
         int i;
         int patternLength=strlen(pattern);
         BOOL match=YES;
         for(i=0; i<patternLength; i++)
         {
            if(bytes[cursor+i]!=pattern[i])
            {
               match=NO;
               break;
            }
         }
         if(match)
         {
            if(start!=0)
            {
               int startOfHeaders=start+2;
               int cursor2=startOfHeaders;
               while((bytes[cursor2]!=(char)0x0d)||(bytes[cursor2+1]!=(char)0x0a)||(bytes[cursor2+2]!=(char)0x0d)||(bytes[cursor2+3]!=(char)0x0a))
               {
                  cursor2++;
                  if(cursor2+4==max)
                  {
                     break;
                  }
               }
               if(cursor2+4==max)
               {
                  break;
               }
               else
               {
                  int lengthOfHeaders=cursor2-startOfHeaders;
                  char *headers=(char *)malloc((lengthOfHeaders+1)*sizeof(char));
                  strncpy(headers, bytes+startOfHeaders, lengthOfHeaders);
                  headers[lengthOfHeaders]=0;

                  NSMutableDictionary *item=parseHeaders(headers);

                  int startOfData=cursor2+4;
                  int lengthOfData=cursor-startOfData-2;

                  if(([item valueForKey:@"Content-Type"]==nil)&&([item valueForKey:@"filename"]==nil))
                  {
                     NSString *string=[[NSString alloc] initWithBytes:(bytes+startOfData) length:lengthOfData encoding:NSUTF8StringEncoding];
                     keyNo++;
                     [dict setObject:string forKey:[NSString stringWithFormat:@"%d", keyNo]];
                  }
                  else
                  {
                     NSData *data=[NSData dataWithBytes:(bytes+startOfData) length:lengthOfData];
                     [item setObject:data forKey:@"data"];
                     keyNo++;
                     [dict setObject:item forKey:[NSString stringWithFormat:@"%d", keyNo]];
                  }
               }
            }
            cursor=cursor+patternLength-1;
            start=cursor+1;
         }
      }
      cursor++;
   }

   return dict;
}

- (NSArray *)multipartArray
{
   NSDictionary *dict=[self multipartDictionary];
   NSArray *keys=[[dict allKeys] sortedArrayUsingSelector:@selector(localizedStandardCompare:)];
   NSMutableArray *array=[NSMutableArray array];
   for(NSString *key in keys)
   {
      [array addObject:dict[key]];
   }
   return array;
}

- (NSDictionary *)multipartDictionary
{
   const char *bytes=(const char *)[self bytes];
   int cursor=0;
   int max=[self length];
   while(cursor<max)
   {
      if(bytes[cursor]==0x0d)
      {
         break;
      }
      else
      {
         cursor++;
      }
   }
   char *pattern=(char *)malloc((cursor+1)*sizeof(char));
   strncpy(pattern, bytes, cursor);
   pattern[cursor]=0x00;
   NSString *boundary=[[NSString alloc] initWithCString:pattern encoding:NSUTF8StringEncoding];
   free(pattern);
   return [self multipartDictionaryWithBoundary:boundary];
}

@end
like image 82
Darkngs Avatar answered Mar 22 '23 23:03

Darkngs


This is an old topic but agree with @possen ("This does not work so great if your parts consist of say UTF-8 and binary data because it tries to treat it as UTF-8, binary data will cause the initWithData function to fail. Instead, you need to stream the data in and handle each type separately for each encoding based upon the content-type.").

If this helps, this is a Swift implementation that handle binary image.

if let multiparts = responseData?.multipartArray(withBoundary: boundary) {
    for part in multiparts {
        if part.contentType == "application/json" {
            let a = try? JSONDecoder().decode(YOUR_DECODABLE_STRUCT.self, from: part.body)
        } else if part.contentType == "image/jpg" {
            let imageData = part.body
        }
    }                    
}
extension Data {
    
    func multipartArray(withBoundary boundary: String, key: String = "Content-Type:") -> [(contentType: String, body: Data)]? {
        func extractBody(_ data: Data) -> Data? {
            guard let startOfLine = key.data(using: .utf8) else { return nil }
            guard let endOfLine = "\r\n".data(using: .utf8) else { return nil }
            var result: Data? = nil
            var pos = data.startIndex
            while let r1 = data[pos...].range(of: startOfLine)
            {
                if let r2 = data[r1.upperBound...].range(of: endOfLine) {
                    pos = r2.upperBound
                }
            }
            
            if pos < data.endIndex {
                result = data[(pos+2)...]
            }
            return result
        }
        
        let multiparts = components(separatedBy: ("--" + boundary))
        var result: [(String, Data)]? = nil
        for part in multiparts
            .enumerated()
            .map({ index, data -> Data in
                if index == multiparts.count-1 {
                    return data.dropLast(2)
                } else {
                    return data
                }
            })
        {
            for contentTypeData in part.slices(between: key, and: "\r") {
                if let contentType = String(data: contentTypeData, encoding: .utf8),
                   let body = extractBody(part)
                {
                    if result == nil {
                        result = [(String, Data)]()
                    }
                    result?.append(
                        (contentType.trimmingCharacters(in: .whitespacesAndNewlines), body)
                    )
                } else {
                    continue
                }
            }
        }
        return result
    }
    
    func slices(between from: String, and to: String) -> [Data] {
        guard let from = from.data(using: .utf8) else { return [] }
        guard let to = to.data(using: .utf8) else { return [] }
        return slices(between: from, and: to)
    }
    
    func slices(between from: Data, and to: Data) -> [Data] {
        var chunks: [Data] = []
        var pos = startIndex
        while let r1 = self[pos...].range(of: from),
              let r2 = self[r1.upperBound...].range(of: to)
        {
            chunks.append(self[r1.upperBound..<r2.lowerBound])
            pos = r1.upperBound
        }
        return chunks
    }
    
    func components(separatedBy separator: String) -> [Data] {
        guard let separator = separator.data(using: .utf8)  else { return [] }
        return components(separatedBy: separator)
    }
    
    func components(separatedBy separator: Data) -> [Data] {
        var chunks: [Data] = []
        var pos = startIndex
        while let r = self[pos...].range(of: separator) {
            if r.lowerBound > pos {
                chunks.append(self[pos..<r.lowerBound])
            }
            pos = r.upperBound
        }
        if pos < endIndex {
            chunks.append(self[pos..<endIndex])
        }
        return chunks
    }
}

like image 33
Paul Bancarel Avatar answered Mar 23 '23 01:03

Paul Bancarel