Skip to content

Commit 79ba449

Browse files
committed
First implementation
1 parent 7895bb2 commit 79ba449

28 files changed

+1829
-12
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ fastlane/Preview.html
6767
fastlane/screenshots
6868
fastlane/test_output
6969

70+
node_modules
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v8.8.1
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const Path = require('path');
4+
const Http = require('http');
5+
const express = require('express');
6+
const multer = require('multer');
7+
const upload = multer();
8+
9+
const PORT = process.env.PORT || 8080;
10+
const HOSTNAME = 'localhost';
11+
12+
const app = express();
13+
14+
15+
app.post('/echo', upload.single("example", 2), (req, res, next) => {
16+
console.log(req.body);
17+
console.log(req.file);
18+
res.sendStatus(200).end();
19+
})
20+
21+
22+
Http.createServer(app).listen(PORT, HOSTNAME, () => {
23+
console.log(`Listening on http://${HOSTNAME}:${PORT}`)
24+
});

Assets/CompatiilityTests/Server/package-lock.json

Lines changed: 435 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "multipart-form-data-swift-tests",
3+
"version": "0.0.0",
4+
"description": "A test server for MultipartFormData",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "Kuniwak",
10+
"license": "MIT",
11+
"dependencies": {
12+
"express": "^4.16.2",
13+
"multer": "^1.3.0"
14+
}
15+
}

MultipartFormData.xcodeproj/project.pbxproj

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,23 @@
2121
/* End PBXAggregateTarget section */
2222

2323
/* Begin PBXBuildFile section */
24+
2475E0D5E2773BA4603E3EC6 /* ContentTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E121FCC1293508346E8F /* ContentTypeTests.swift */; };
25+
2475E1A162712F9F649AB0AD /* ValidationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E8AA054C727A29EC7681 /* ValidationResult.swift */; };
26+
2475E2281C2495E07D6D8CA9 /* BoundaryGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E4B7D2F723F738F2E007 /* BoundaryGenerator.swift */; };
27+
2475E2FF4351A87B272D0F7B /* ContentDispositionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E32FCACCA7407F33C0CD /* ContentDispositionTests.swift */; };
28+
2475E3509B820ACA79023A1C /* MultipartFormData+Part.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EA5AF67DBA364CF5E4B2 /* MultipartFormData+Part.swift */; };
29+
2475E4A890F7A4A5639BC643 /* CRLF.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E81411D9D559F7F10630 /* CRLF.swift */; };
30+
2475E579CBDABF38E0B9D480 /* MIMEType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E29B2FE43643D1A7E39D /* MIMEType.swift */; };
31+
2475E5DAEB0C0094029E813D /* ContentDisposition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475ED4161A2BCF026D922F5 /* ContentDisposition.swift */; };
32+
2475E5F76D34B19B9BC1D2ED /* NameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EF79EA7D4B425B9ED84D /* NameTests.swift */; };
33+
2475E76801BAF80A8500EFC6 /* Dash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E47794278EE806B3956C /* Dash.swift */; };
34+
2475E8E2C02D072A637A6591 /* MultipartFormDataBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EB96AB961A924C28CB1F /* MultipartFormDataBuilderTests.swift */; };
35+
2475E98937B5365758B34197 /* CompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E2BEB8A5CCE0A6F29A00 /* CompatibilityTests.swift */; };
36+
2475EA665B4BC80AEAA8A1BA /* Filename.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475EE4FF72DC0E3F21392BB /* Filename.swift */; };
37+
2475EB84CA24B272BBF444B6 /* Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E5AFA7B3F8B6E8EC728B /* Name.swift */; };
38+
2475EDF70A4FE58583DDD6B7 /* FilenameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E0CB6E13CDE491E6777F /* FilenameTests.swift */; };
39+
2475EE512EA38B1FF3C31C56 /* ContentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E7966BB1F03216D254FB /* ContentType.swift */; };
40+
2475EE92C74A97CAB4C14121 /* MultipartFormData+Builder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2475E5EC12E2409115CD01E9 /* MultipartFormData+Builder.swift */; };
2441
OBJ_21 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
2542
OBJ_27 /* MultipartFormDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* MultipartFormDataTests.swift */; };
2643
OBJ_29 /* MultipartFormData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "MultipartFormData::MultipartFormData::Product" /* MultipartFormData.framework */; };
@@ -32,19 +49,36 @@
3249
isa = PBXContainerItemProxy;
3350
containerPortal = OBJ_1 /* Project object */;
3451
proxyType = 1;
35-
remoteGlobalIDString = "MultipartFormData::MultipartFormData";
52+
remoteGlobalIDString = MultipartFormData::MultipartFormData;
3653
remoteInfo = MultipartFormData;
3754
};
3855
62858A5C1FA4EE2E0051CAED /* PBXContainerItemProxy */ = {
3956
isa = PBXContainerItemProxy;
4057
containerPortal = OBJ_1 /* Project object */;
4158
proxyType = 1;
42-
remoteGlobalIDString = "MultipartFormData::MultipartFormDataTests";
59+
remoteGlobalIDString = MultipartFormData::MultipartFormDataTests;
4360
remoteInfo = MultipartFormDataTests;
4461
};
4562
/* End PBXContainerItemProxy section */
4663

4764
/* Begin PBXFileReference section */
65+
2475E0CB6E13CDE491E6777F /* FilenameTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilenameTests.swift; sourceTree = "<group>"; };
66+
2475E121FCC1293508346E8F /* ContentTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentTypeTests.swift; sourceTree = "<group>"; };
67+
2475E29B2FE43643D1A7E39D /* MIMEType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MIMEType.swift; sourceTree = "<group>"; };
68+
2475E2BEB8A5CCE0A6F29A00 /* CompatibilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CompatibilityTests.swift; sourceTree = "<group>"; };
69+
2475E32FCACCA7407F33C0CD /* ContentDispositionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentDispositionTests.swift; sourceTree = "<group>"; };
70+
2475E47794278EE806B3956C /* Dash.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dash.swift; sourceTree = "<group>"; };
71+
2475E4B7D2F723F738F2E007 /* BoundaryGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoundaryGenerator.swift; sourceTree = "<group>"; };
72+
2475E5AFA7B3F8B6E8EC728B /* Name.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Name.swift; sourceTree = "<group>"; };
73+
2475E5EC12E2409115CD01E9 /* MultipartFormData+Builder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MultipartFormData+Builder.swift"; sourceTree = "<group>"; };
74+
2475E7966BB1F03216D254FB /* ContentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentType.swift; sourceTree = "<group>"; };
75+
2475E81411D9D559F7F10630 /* CRLF.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRLF.swift; sourceTree = "<group>"; };
76+
2475E8AA054C727A29EC7681 /* ValidationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValidationResult.swift; sourceTree = "<group>"; };
77+
2475EA5AF67DBA364CF5E4B2 /* MultipartFormData+Part.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MultipartFormData+Part.swift"; sourceTree = "<group>"; };
78+
2475EB96AB961A924C28CB1F /* MultipartFormDataBuilderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormDataBuilderTests.swift; sourceTree = "<group>"; };
79+
2475ED4161A2BCF026D922F5 /* ContentDisposition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentDisposition.swift; sourceTree = "<group>"; };
80+
2475EE4FF72DC0E3F21392BB /* Filename.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Filename.swift; sourceTree = "<group>"; };
81+
2475EF79EA7D4B425B9ED84D /* NameTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NameTests.swift; sourceTree = "<group>"; };
4882
"MultipartFormData::MultipartFormData::Product" /* MultipartFormData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = MultipartFormData.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4983
"MultipartFormData::MultipartFormDataTests::Product" /* MultipartFormDataTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = MultipartFormDataTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
5084
OBJ_12 /* MultipartFormDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipartFormDataTests.swift; sourceTree = "<group>"; };
@@ -83,6 +117,12 @@
83117
isa = PBXGroup;
84118
children = (
85119
OBJ_12 /* MultipartFormDataTests.swift */,
120+
2475E32FCACCA7407F33C0CD /* ContentDispositionTests.swift */,
121+
2475E121FCC1293508346E8F /* ContentTypeTests.swift */,
122+
2475E0CB6E13CDE491E6777F /* FilenameTests.swift */,
123+
2475EF79EA7D4B425B9ED84D /* NameTests.swift */,
124+
2475EB96AB961A924C28CB1F /* MultipartFormDataBuilderTests.swift */,
125+
2475E2BEB8A5CCE0A6F29A00 /* CompatibilityTests.swift */,
86126
);
87127
name = MultipartFormDataTests;
88128
path = Tests/MultipartFormDataTests;
@@ -120,6 +160,17 @@
120160
isa = PBXGroup;
121161
children = (
122162
OBJ_9 /* MultipartFormData.swift */,
163+
2475EE4FF72DC0E3F21392BB /* Filename.swift */,
164+
2475E8AA054C727A29EC7681 /* ValidationResult.swift */,
165+
2475E5AFA7B3F8B6E8EC728B /* Name.swift */,
166+
2475ED4161A2BCF026D922F5 /* ContentDisposition.swift */,
167+
2475E7966BB1F03216D254FB /* ContentType.swift */,
168+
2475E29B2FE43643D1A7E39D /* MIMEType.swift */,
169+
2475E81411D9D559F7F10630 /* CRLF.swift */,
170+
2475E47794278EE806B3956C /* Dash.swift */,
171+
2475E4B7D2F723F738F2E007 /* BoundaryGenerator.swift */,
172+
2475E5EC12E2409115CD01E9 /* MultipartFormData+Builder.swift */,
173+
2475EA5AF67DBA364CF5E4B2 /* MultipartFormData+Part.swift */,
123174
);
124175
name = MultipartFormData;
125176
path = Sources/MultipartFormData;
@@ -217,6 +268,12 @@
217268
buildActionMask = 0;
218269
files = (
219270
OBJ_27 /* MultipartFormDataTests.swift in Sources */,
271+
2475E2FF4351A87B272D0F7B /* ContentDispositionTests.swift in Sources */,
272+
2475E0D5E2773BA4603E3EC6 /* ContentTypeTests.swift in Sources */,
273+
2475EDF70A4FE58583DDD6B7 /* FilenameTests.swift in Sources */,
274+
2475E5F76D34B19B9BC1D2ED /* NameTests.swift in Sources */,
275+
2475E8E2C02D072A637A6591 /* MultipartFormDataBuilderTests.swift in Sources */,
276+
2475E98937B5365758B34197 /* CompatibilityTests.swift in Sources */,
220277
);
221278
runOnlyForDeploymentPostprocessing = 0;
222279
};
@@ -225,6 +282,17 @@
225282
buildActionMask = 0;
226283
files = (
227284
OBJ_36 /* MultipartFormData.swift in Sources */,
285+
2475E1A162712F9F649AB0AD /* ValidationResult.swift in Sources */,
286+
2475EA665B4BC80AEAA8A1BA /* Filename.swift in Sources */,
287+
2475EB84CA24B272BBF444B6 /* Name.swift in Sources */,
288+
2475E5DAEB0C0094029E813D /* ContentDisposition.swift in Sources */,
289+
2475EE512EA38B1FF3C31C56 /* ContentType.swift in Sources */,
290+
2475E579CBDABF38E0B9D480 /* MIMEType.swift in Sources */,
291+
2475E4A890F7A4A5639BC643 /* CRLF.swift in Sources */,
292+
2475E76801BAF80A8500EFC6 /* Dash.swift in Sources */,
293+
2475E2281C2495E07D6D8CA9 /* BoundaryGenerator.swift in Sources */,
294+
2475EE92C74A97CAB4C14121 /* MultipartFormData+Builder.swift in Sources */,
295+
2475E3509B820ACA79023A1C /* MultipartFormData+Part.swift in Sources */,
228296
);
229297
runOnlyForDeploymentPostprocessing = 0;
230298
};
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
version = "1.3">
4+
<BuildAction
5+
parallelizeBuildables = "YES"
6+
buildImplicitDependencies = "YES">
7+
<BuildActionEntries>
8+
<BuildActionEntry
9+
buildForTesting = "YES"
10+
buildForRunning = "YES"
11+
buildForProfiling = "YES"
12+
buildForArchiving = "YES"
13+
buildForAnalyzing = "YES">
14+
<BuildableReference
15+
BuildableIdentifier = "primary"
16+
BlueprintIdentifier = "MultipartFormData::MultipartFormDataTests"
17+
BuildableName = "MultipartFormDataTests.xctest"
18+
BlueprintName = "MultipartFormDataTests"
19+
ReferencedContainer = "container:MultipartFormData.xcodeproj">
20+
</BuildableReference>
21+
</BuildActionEntry>
22+
</BuildActionEntries>
23+
</BuildAction>
24+
<TestAction
25+
buildConfiguration = "Debug"
26+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
27+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
28+
language = ""
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
<TestableReference
32+
skipped = "NO">
33+
<BuildableReference
34+
BuildableIdentifier = "primary"
35+
BlueprintIdentifier = "MultipartFormData::MultipartFormDataTests"
36+
BuildableName = "MultipartFormDataTests.xctest"
37+
BlueprintName = "MultipartFormDataTests"
38+
ReferencedContainer = "container:MultipartFormData.xcodeproj">
39+
</BuildableReference>
40+
</TestableReference>
41+
</Testables>
42+
<AdditionalOptions>
43+
</AdditionalOptions>
44+
</TestAction>
45+
<LaunchAction
46+
buildConfiguration = "Debug"
47+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
48+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
49+
language = ""
50+
launchStyle = "0"
51+
useCustomWorkingDirectory = "NO"
52+
ignoresPersistentStateOnLaunch = "NO"
53+
debugDocumentVersioning = "YES"
54+
debugServiceExtension = "internal"
55+
allowLocationSimulation = "YES">
56+
<MacroExpansion>
57+
<BuildableReference
58+
BuildableIdentifier = "primary"
59+
BlueprintIdentifier = "MultipartFormData::MultipartFormDataTests"
60+
BuildableName = "MultipartFormDataTests.xctest"
61+
BlueprintName = "MultipartFormDataTests"
62+
ReferencedContainer = "container:MultipartFormData.xcodeproj">
63+
</BuildableReference>
64+
</MacroExpansion>
65+
<AdditionalOptions>
66+
</AdditionalOptions>
67+
</LaunchAction>
68+
<ProfileAction
69+
buildConfiguration = "Release"
70+
shouldUseLaunchSchemeArgsEnv = "YES"
71+
savedToolIdentifier = ""
72+
useCustomWorkingDirectory = "NO"
73+
debugDocumentVersioning = "YES">
74+
</ProfileAction>
75+
<AnalyzeAction
76+
buildConfiguration = "Debug">
77+
</AnalyzeAction>
78+
<ArchiveAction
79+
buildConfiguration = "Release"
80+
revealArchiveInOrganizer = "YES">
81+
</ArchiveAction>
82+
</Scheme>

README.md

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,103 @@
1-
# MultipartFormData
1+
MultipartFormData for Swift
2+
===========================
23

3-
A description of this package.
4+
MultipartFormData for Swift.
5+
6+
7+
Basic Usage
8+
-----------
9+
10+
```swift
11+
let result = MultipartFormData.Builder(
12+
generatingBoundaryBy: RandomBoundaryGenerator()
13+
)
14+
.build(with: [
15+
(
16+
name: "example1",
17+
filename: nil,
18+
mimeType: nil,
19+
data: "Hello, World!".data(using: .utf8)!
20+
),
21+
(
22+
name: "example2",
23+
filename: "example.txt",
24+
mimeType: MIMEType.textPlain,
25+
data: "EXAMPLE_TXT".data(using: .utf8)!
26+
),
27+
])
28+
29+
30+
switch result {
31+
case let .valid(multipartFormData):
32+
var requrst = URLRequest(url: URL(string: "http://example.com")!)
33+
request.addValue(multipartFormData.contentType, forHTTPHeaderField: "Content-Type")
34+
request.httpBody = multipartFormData.body
35+
36+
let task = URLSession.shared.dataTask(with: request)
37+
task.resume()
38+
39+
case let .invalid(because: error):
40+
print(error)
41+
return
42+
}
43+
```
44+
45+
46+
47+
Advanced Usage
48+
--------------
49+
50+
```swift
51+
let multipartFormData = MultipartFormData(
52+
uniqueAndValidLengthBoundary: "boundary",
53+
body: [
54+
MultipartFormData.Part(
55+
contentDisposition: ContentDisposition(
56+
name: Name(asPercentEncoded: "field1"),
57+
filename: nil
58+
),
59+
contentType: nil,
60+
content: "value1".data(using: .utf8)!
61+
),
62+
MultipartFormData.Part(
63+
contentDisposition: ContentDisposition(
64+
name: Name(asPercentEncoded: "field2"),
65+
filename: Filename(asPercentEncoded: "example.txt")
66+
),
67+
contentType: ContentType(representing: .textPlain),
68+
content: "value2".data(using: .utf8)!
69+
),
70+
]
71+
)
72+
73+
print(multipartFormData.header.name)
74+
// Content-Type
75+
76+
print(multipartFormData.header.value)
77+
// multipart/form-data; boundary="boundary"
78+
79+
switch multipartFormData.asData() {
80+
case let .valid(data):
81+
print(String(data: data, encoding: .utf8))
82+
83+
// --boundary
84+
// Content-Disposition: form-data; name="field1"
85+
//
86+
// value1
87+
// --boundary
88+
// Content-Disposition: form-data; name="filed2"; filename="example.txt"
89+
// Content-Type: text/plain
90+
//
91+
// value2
92+
// --boundary--
93+
94+
case let .invalid(error):
95+
print(error)
96+
}
97+
```
98+
99+
100+
License
101+
-------
102+
103+
MIT
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Foundation
2+
3+
4+
5+
public protocol BoundaryGenerator {
6+
func generate() -> String
7+
}
8+
9+
10+
11+
public class ConstantBoundaryGenerator: BoundaryGenerator {
12+
private let boundary: String
13+
14+
15+
public init(willReturn boundary: String) {
16+
self.boundary = boundary
17+
}
18+
19+
20+
public func generate() -> String {
21+
return self.boundary
22+
}
23+
}
24+
25+
26+
27+
public class RandomBoundaryGenerator: BoundaryGenerator {
28+
public func generate() -> String {
29+
return String(format: "%08x%08x", arc4random(), arc4random())
30+
}
31+
}

0 commit comments

Comments
 (0)