forked from jiacai2050/zig-curl
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMultiPart.zig
More file actions
179 lines (159 loc) · 6.27 KB
/
Copy pathMultiPart.zig
File metadata and controls
179 lines (159 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! MultiPart module provides support for multipart/form-data requests.
//!
//! It allows you to create and manage multipart form data, which is commonly used for file uploads and form submissions in HTTP requests.
//!
//! More see: https://curl.se/libcurl/c/curl_mime_init.html
const std = @import("std");
const util = @import("util.zig");
const Easy = @import("Easy.zig");
const c = util.c;
const checkCode = @import("errors.zig").checkCode;
const Reader = std.Io.Reader;
mime_handle: *c.curl_mime,
/// `NonCopyingData` allows setting a mime part's body data from a custom source without copying the data.
///
/// Modelled after [curl_mime_data_cb](https://curl.se/libcurl/c/curl_mime_data_cb.html).
/// There are two built-in implementations: `SliceBased` and `ReaderBased`.
/// One can also implement their own source by providing a `VTable`.
pub const NonCopyingData = struct {
size: usize,
ptr: *anyopaque,
vtable: *const VTable,
const VTable = struct {
// ?*const fn ([*c]u8, usize, usize, ?*anyopaque) callconv(.c) usize;
read: c.curl_read_callback,
// ?*const fn (?*anyopaque, curl_off_t, c_int) callconv(.c) c_int;
seek: c.curl_seek_callback,
// ?*const fn (?*anyopaque) callconv(.c) void;
free: c.curl_free_callback,
};
/// A `NonCopyingData` source based on a slice.
/// The slice data will not be copied, so it must remain valid until the request is completed.
/// It support `read` and `seek` operations.
pub const SliceBased = struct {
data_with_offset: DataWithOffset,
const DataWithOffset = struct {
slice: []const u8,
offset: usize,
};
pub fn init(slice: []const u8) SliceBased {
return .{
.data_with_offset = .{
.slice = slice,
.offset = 0,
},
};
}
pub fn nonCopying(self: *SliceBased) NonCopyingData {
return .{
.size = self.data_with_offset.slice.len,
.ptr = @ptrCast(&self.data_with_offset),
.vtable = &.{
.read = read,
.seek = seek,
.free = null,
},
};
}
fn read(dest: [*c]u8, size: usize, nmemb: usize, user_data: ?*anyopaque) callconv(.c) usize {
var source: *DataWithOffset = @ptrCast(@alignCast(user_data orelse return c.CURL_READFUNC_ABORT));
var to_read = size * nmemb;
const remaining = source.slice.len - source.offset;
if (to_read > remaining) {
to_read = remaining;
}
std.mem.copyForwards(u8, dest[0..to_read], source.slice[source.offset .. source.offset + to_read]);
source.offset += to_read;
return to_read;
}
pub fn seek(user_data: ?*anyopaque, offset: c.curl_off_t, origin: c_int) callconv(.c) c_int {
var source: *DataWithOffset = @ptrCast(@alignCast(user_data orelse return c.CURL_SEEKFUNC_FAIL));
const new_pos = switch (origin) {
c.SEEK_SET => offset,
c.SEEK_CUR => offset + @as(c.curl_off_t, @intCast(source.offset)),
else => return c.CURL_SEEKFUNC_FAIL,
};
if (new_pos < 0) {
return c.CURL_SEEKFUNC_FAIL;
}
const new_offset = @as(usize, @intCast(new_pos));
if (new_offset > source.slice.len) {
return c.CURL_SEEKFUNC_FAIL;
}
source.offset = new_offset;
return c.CURL_SEEKFUNC_OK;
}
};
/// A `NonCopyingData` source based on a `Reader`.
/// The reader must remain valid until the request is completed.
/// It only support `read` operation.
pub const ReaderBased = struct {
reader: *Reader,
size: usize,
pub fn init(size: usize, reader: *Reader) ReaderBased {
return .{
.reader = reader,
.size = size,
};
}
pub fn nonCopying(self: *ReaderBased) NonCopyingData {
return .{
.size = self.size,
.ptr = @ptrCast(self.reader),
.vtable = &.{
.read = read,
.seek = null,
.free = null,
},
};
}
fn read(dest: [*c]u8, size: usize, nmemb: usize, user_data: ?*anyopaque) callconv(.c) usize {
var source: *Reader = @ptrCast(@alignCast(user_data orelse return c.CURL_READFUNC_ABORT));
const to_read = size * nmemb;
const n = source.readSliceShort(dest[0..to_read]) catch return c.CURL_READFUNC_ABORT;
return n;
}
};
};
pub const DataSource = union(enum) {
/// Set a mime part's body content from memory data.
/// Data will get copied when send request.
/// Setting large data is memory consuming: one might consider using `non_copying` in such a case.
data: []const u8,
/// Set a mime part's body data from a file contents.
file: [:0]const u8,
/// Data will NOT get copied when send request, so the data source must remain valid until
/// the request is completed.
non_copying: NonCopyingData,
};
pub fn init(easy: Easy) !@This() {
const mime_handle = if (c.curl_mime_init(easy.handle)) |mh| mh else return error.MimeInit;
return .{
.mime_handle = mime_handle,
};
}
pub fn deinit(self: @This()) void {
c.curl_mime_free(self.mime_handle);
}
pub fn addPart(self: @This(), name: [:0]const u8, source: DataSource) !void {
const part = if (c.curl_mime_addpart(self.mime_handle)) |part| part else return error.MimeAddPart;
try checkCode(c.curl_mime_name(part, name));
switch (source) {
.data => |slice| {
try checkCode(c.curl_mime_data(part, slice.ptr, slice.len));
},
.file => |filepath| {
try checkCode(c.curl_mime_filedata(part, filepath));
},
.non_copying => |ncdata| {
try checkCode(c.curl_mime_data_cb(
part,
@intCast(ncdata.size),
ncdata.vtable.read,
ncdata.vtable.seek,
ncdata.vtable.free,
ncdata.ptr,
));
},
}
}