Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to upload file on iOS? #92

Open
pzmudzinski opened this issue Dec 27, 2023 · 11 comments
Open

How to upload file on iOS? #92

pzmudzinski opened this issue Dec 27, 2023 · 11 comments
Labels
bug Something isn't working

Comments

@pzmudzinski
Copy link

pzmudzinski commented Dec 27, 2023

I was trying to encode struct like that:

struct MyStruct {
    let imageFile: Data
}
let encoder = FormDataEncoder()
let body = try encoder.encode(options, boundary: boundary)

But imageFile is not recognized as file part. Also I could not find any example.

Also sorry for bug label - don't know how to remove it 😅

@pzmudzinski pzmudzinski added the bug Something isn't working label Dec 27, 2023
@0xTim
Copy link
Member

0xTim commented Jan 2, 2024

What do you mean by

But imageFile is not recognized as file part

Is that error coming from the encode function? I tried to find a test that shows Data being encoded but I can't which isn't great...

@pzmudzinski
Copy link
Author

I mean it's not encoded in such format:

Content-Disposition: attachment; filename="yourfilename"

I haven't found any way to use it to encode form with file and just gave up and spin up own solution.

@0xTim
Copy link
Member

0xTim commented Jan 3, 2024

Content-Disposition is a response header when used with the attachment type, not a request header so I'm confused as to why you need it when doing encoding?

@pzmudzinski
Copy link
Author

Sorry, I meant "filename" part. Basically was trying to use this library to make a request with multipart form-data body containing both regular key - values and a file within. So in the end it would look like:

Content-Disposition: form-data; name=some-key
...
some value
...
Content-Disposition: form-data; name=image_input; filename=something.png
...
file content
...

But I have not found a way to encode file using it. Does it make sense?

@0xTim
Copy link
Member

0xTim commented Jan 6, 2024

Yes it does make sense. Have you tried File instead of Data? That should encode correctly and is a bug if not

@pzmudzinski
Copy link
Author

I am not sure what are you referring to. There is no such thing as File In Foundation framework.

@0xTim
Copy link
Member

0xTim commented Jan 7, 2024

Sorry it's a Vapor thing https://github.com/vapor/vapor/blob/main/Sources/Vapor/Utilities/File.swift

If you're trying to use this outside of Vapor you can copy and paste it and I think it will work

@sidepelican
Copy link

sidepelican commented Jan 14, 2024

When using Vapor.File, it gets encoded as follows:

-----bound5952344343153968676
Content-Disposition: form-data; name="image[data]"

<binary data>
-----bound5952344343153968676
Content-Disposition: form-data; name="image[filename]"

myfile.jpg

However, it seems like @pzmudzinski is expecting it to be encoded like this:

Content-Disposition: form-data; name=image; filename=myfile.jpg

I also agree with this, and it appears that without this format, some runtimes may not recognize the file as such.

@sidepelican
Copy link

Sorry, I had missed the extension in Vapor that addresses this issue.
With this extension, it would work as expected

https://github.com/vapor/vapor/blob/0680f9f6bfab7100cd585b3186740ee7860c983e/Sources/Vapor/Multipart/File%2BMultipart.swift#L4

@sidepelican
Copy link

sidepelican commented Jan 15, 2024

@pzmudzinski Here is the simple File.swift

import Foundation
import MultipartKit
import NIOFoundationCompat

public struct File: Codable, Equatable, Sendable, MultipartPartConvertible {
    public var filename: String
    public var data: Data
    public var contentType: String?

    public init(data: Data, filename: String, contentType: String? = nil) {
        self.data = data
        self.filename = filename
        self.contentType = contentType
    }

    // MARK: - MultipartPartConvertible

    public var multipart: MultipartPart? {
        var part = MultipartPart(headers: [:], body: data)
        if let contentType {
            part.headers.replaceOrAdd(name: "Content-Type", value: contentType)
        }
        part.headers.replaceOrAdd(
            name: "Content-Disposition",
            value: !filename.contains("\"")
            ? "form-data; filename=\"\(filename)\""
            : "form-data; filename='\(filename)'"
        )
        return part
    }

    public init?(multipart: MultipartPart) {
        let filenameRegex = /filename=(?:"([^"]+)"|'([^']+)'|([^\s"';]+))/
        guard let contentDisposition = multipart.headers.first(name: "Content-Disposition"),
              let output = contentDisposition.firstMatch(of: filenameRegex)?.output,
              let filename = output.1 ?? output.2 ?? output.3
        else {
            return nil
        }
        self.init(
            data: Data(buffer: multipart.body),
            filename: String(filename),
            contentType: multipart.headers.first(name: "Content-Type")
        )
    }
}

@0xTim
Copy link
Member

0xTim commented Jan 15, 2024

@pzmudzinski Does this solve your issue? File probably should be part of MultipartKit but it would be a breaking change to move it as this point I think, right @gwynne ? (We'd end up with duplicated symbols due to the re-export), so if you're not pulling in Vapor as well you'll need to copy in File

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants