Skip to content

Commit 4380f5a

Browse files
committed
feat(nextcloud): Add support for checksums for uploading WebDAV files
Signed-off-by: provokateurin <kate@provokateurin.de>
1 parent eed9a0c commit 4380f5a

File tree

3 files changed

+74
-0
lines changed

3 files changed

+74
-0
lines changed

packages/nextcloud/lib/src/api/webdav/webdav_client.dart

+41
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ import 'package:nextcloud/src/api/webdav/webdav.dart';
1010
import 'package:nextcloud/utils.dart';
1111
import 'package:universal_io/io.dart' show File, FileStat;
1212

13+
/// The algorithms supported for the oc:checksum prop and OC-Checksum header.
14+
const supportedChecksumAlgorithms = ['md5', 'sha1', 'sha256', 'sha3-256', 'adler32'];
15+
16+
/// The pattern of supported checksum algorithms.
17+
///
18+
/// It has to be `<algorithm>:<hash>` with `algorithm` being one of [supportedChecksumAlgorithms].
19+
/// The checksum is case-insensitive.
20+
final checksumPattern = RegExp(
21+
'^(${supportedChecksumAlgorithms.join('|')}):.+\$',
22+
caseSensitive: false,
23+
);
24+
1325
/// WebDavClient class
1426
class WebDavClient {
1527
// ignore: public_member_api_docs
@@ -92,13 +104,16 @@ class WebDavClient {
92104

93105
/// Request to put a new file at [path] with [localData] as content.
94106
///
107+
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
108+
///
95109
/// See:
96110
/// * [put] for a complete operation executing this request.
97111
http.Request put_Request(
98112
Uint8List localData,
99113
PathUri path, {
100114
DateTime? lastModified,
101115
DateTime? created,
116+
String? checksum,
102117
}) {
103118
final request = http.Request('PUT', _constructUri(path))..bodyBytes = localData;
104119

@@ -107,6 +122,7 @@ class WebDavClient {
107122
lastModified: lastModified,
108123
created: created,
109124
contentLength: localData.length,
125+
checksum: checksum,
110126
);
111127
_addBaseHeaders(request);
112128
return request;
@@ -116,6 +132,8 @@ class WebDavClient {
116132
///
117133
/// [lastModified] sets the date when the file was last modified on the server.
118134
/// [created] sets the date when the file was created on the server.
135+
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
136+
///
119137
/// See:
120138
/// * http://www.webdav.org/specs/rfc2518.html#METHOD_PUT for more information.
121139
/// * [put_Request] for the request sent by this method.
@@ -124,19 +142,23 @@ class WebDavClient {
124142
PathUri path, {
125143
DateTime? lastModified,
126144
DateTime? created,
145+
String? checksum,
127146
}) {
128147
final request = put_Request(
129148
localData,
130149
path,
131150
lastModified: lastModified,
132151
created: created,
152+
checksum: checksum,
133153
);
134154

135155
return csrfClient.send(request);
136156
}
137157

138158
/// Request to put a new file at [path] with [localData] as content.
139159
///
160+
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
161+
///
140162
/// See:
141163
/// * [putStream] for a complete operation executing this request.
142164
http.StreamedRequest putStream_Request(
@@ -145,6 +167,7 @@ class WebDavClient {
145167
required int contentLength,
146168
DateTime? lastModified,
147169
DateTime? created,
170+
String? checksum,
148171
void Function(double progress)? onProgress,
149172
}) {
150173
final request = http.StreamedRequest('PUT', _constructUri(path));
@@ -155,6 +178,7 @@ class WebDavClient {
155178
lastModified: lastModified,
156179
created: created,
157180
contentLength: contentLength,
181+
checksum: checksum,
158182
);
159183

160184
if (onProgress != null) {
@@ -181,6 +205,7 @@ class WebDavClient {
181205
/// [lastModified] sets the date when the file was last modified on the server.
182206
/// [created] sets the date when the file was created on the server.
183207
/// [contentLength] sets the length of the [localData] that is uploaded.
208+
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
184209
/// [onProgress] can be used to watch the upload progress. Possible values range from 0.0 to 1.0. [contentLength] needs to be set for it to work.
185210
///
186211
/// See:
@@ -192,13 +217,15 @@ class WebDavClient {
192217
required int contentLength,
193218
DateTime? lastModified,
194219
DateTime? created,
220+
String? checksum,
195221
void Function(double progress)? onProgress,
196222
}) {
197223
final request = putStream_Request(
198224
localData,
199225
path,
200226
lastModified: lastModified,
201227
created: created,
228+
checksum: checksum,
202229
contentLength: contentLength,
203230
onProgress: onProgress,
204231
);
@@ -208,6 +235,8 @@ class WebDavClient {
208235

209236
/// Request to put a new file at [path] with [file] as content.
210237
///
238+
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
239+
///
211240
/// See:
212241
/// * [putFile] for a complete operation executing this request.
213242
http.StreamedRequest putFile_Request(
@@ -216,6 +245,7 @@ class WebDavClient {
216245
PathUri path, {
217246
DateTime? lastModified,
218247
DateTime? created,
248+
String? checksum,
219249
void Function(double progress)? onProgress,
220250
}) {
221251
// Authentication and content-type headers are already set by the putStream_Request.
@@ -225,6 +255,7 @@ class WebDavClient {
225255
path,
226256
lastModified: lastModified,
227257
created: created,
258+
checksum: checksum,
228259
contentLength: fileStat.size,
229260
onProgress: onProgress,
230261
);
@@ -234,6 +265,7 @@ class WebDavClient {
234265
///
235266
/// [lastModified] sets the date when the file was last modified on the server.
236267
/// [created] sets the date when the file was created on the server.
268+
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
237269
/// [onProgress] can be used to watch the upload progress. Possible values range from 0.0 to 1.0.
238270
/// See:
239271
/// * http://www.webdav.org/specs/rfc2518.html#METHOD_PUT for more information.
@@ -244,6 +276,7 @@ class WebDavClient {
244276
PathUri path, {
245277
DateTime? lastModified,
246278
DateTime? created,
279+
String? checksum,
247280
void Function(double progress)? onProgress,
248281
}) {
249282
final request = putFile_Request(
@@ -252,6 +285,7 @@ class WebDavClient {
252285
path,
253286
lastModified: lastModified,
254287
created: created,
288+
checksum: checksum,
255289
onProgress: onProgress,
256290
);
257291

@@ -571,13 +605,20 @@ class WebDavClient {
571605
required int contentLength,
572606
DateTime? lastModified,
573607
DateTime? created,
608+
String? checksum,
574609
}) {
575610
if (lastModified != null) {
576611
request.headers['X-OC-Mtime'] = lastModified.secondsSinceEpoch.toString();
577612
}
578613
if (created != null) {
579614
request.headers['X-OC-CTime'] = created.secondsSinceEpoch.toString();
580615
}
616+
if (checksum != null) {
617+
if (!checksumPattern.hasMatch(checksum)) {
618+
throw ArgumentError.value(checksum, 'checksum', 'Invalid checksum');
619+
}
620+
request.headers['OC-Checksum'] = checksum;
621+
}
581622
request.headers['content-length'] = contentLength.toString();
582623
}
583624

packages/nextcloud/test/api/webdav/webdav_test.dart

+19
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,25 @@ void main() {
451451
expect(props.ocTags!.tags!.single, 'example');
452452
});
453453

454+
test('Upload file with checksum', () async {
455+
final file = File('test/files/test.png');
456+
await tester.client.webdav.putFile(
457+
file,
458+
file.statSync(),
459+
PathUri.parse('test/checksum.png'),
460+
checksum: 'md5:abc',
461+
);
462+
463+
final response = await tester.client.webdav.propfind(
464+
PathUri.parse('test/checksum.png'),
465+
prop: const WebDavPropWithoutValues.fromBools(
466+
ocChecksums: true,
467+
),
468+
);
469+
final props = response.responses.single.propstats.first.prop;
470+
expect(props.ocChecksums!.checksums!.single, 'md5:abc');
471+
});
472+
454473
test('Upload and download file', () async {
455474
final destinationDir = Directory.systemTemp.createTempSync();
456475
final destination = File('${destinationDir.path}/test.png');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
PUT http://localhost/remote\.php/webdav/test/checksum\.png
2+
authorization: Bearer mock
3+
content-length: 8650
4+
content-type: application/xml
5+
oc-checksum: md5:abc
6+
ocs-apirequest: true
7+
requesttoken: token
8+
.+
9+
PROPFIND http://localhost/remote\.php/webdav/test/checksum\.png
10+
authorization: Bearer mock
11+
content-type: application/xml
12+
ocs-apirequest: true
13+
requesttoken: token
14+
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud\.org/ns" xmlns:nc="http://nextcloud\.org/ns" xmlns:ocs="http://open-collaboration-services\.org/ns" xmlns:ocm="http://open-cloud-mesh\.org/ns"><d:prop><oc:checksums/></d:prop></d:propfind>

0 commit comments

Comments
 (0)