Skip to content

Commit

Permalink
feat(nextcloud): Add support for checksums for uploading WebDAV files
Browse files Browse the repository at this point in the history
Signed-off-by: provokateurin <kate@provokateurin.de>
  • Loading branch information
provokateurin committed Aug 29, 2024
1 parent eed9a0c commit 4380f5a
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 0 deletions.
41 changes: 41 additions & 0 deletions packages/nextcloud/lib/src/api/webdav/webdav_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ import 'package:nextcloud/src/api/webdav/webdav.dart';
import 'package:nextcloud/utils.dart';
import 'package:universal_io/io.dart' show File, FileStat;

/// The algorithms supported for the oc:checksum prop and OC-Checksum header.
const supportedChecksumAlgorithms = ['md5', 'sha1', 'sha256', 'sha3-256', 'adler32'];

/// The pattern of supported checksum algorithms.
///
/// It has to be `<algorithm>:<hash>` with `algorithm` being one of [supportedChecksumAlgorithms].
/// The checksum is case-insensitive.
final checksumPattern = RegExp(
'^(${supportedChecksumAlgorithms.join('|')}):.+\$',
caseSensitive: false,
);

/// WebDavClient class
class WebDavClient {
// ignore: public_member_api_docs
Expand Down Expand Up @@ -92,13 +104,16 @@ class WebDavClient {

/// Request to put a new file at [path] with [localData] as content.
///
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
///
/// See:
/// * [put] for a complete operation executing this request.
http.Request put_Request(
Uint8List localData,
PathUri path, {
DateTime? lastModified,
DateTime? created,
String? checksum,
}) {
final request = http.Request('PUT', _constructUri(path))..bodyBytes = localData;

Expand All @@ -107,6 +122,7 @@ class WebDavClient {
lastModified: lastModified,
created: created,
contentLength: localData.length,
checksum: checksum,
);
_addBaseHeaders(request);
return request;
Expand All @@ -116,6 +132,8 @@ class WebDavClient {
///
/// [lastModified] sets the date when the file was last modified on the server.
/// [created] sets the date when the file was created on the server.
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
///
/// See:
/// * http://www.webdav.org/specs/rfc2518.html#METHOD_PUT for more information.
/// * [put_Request] for the request sent by this method.
Expand All @@ -124,19 +142,23 @@ class WebDavClient {
PathUri path, {
DateTime? lastModified,
DateTime? created,
String? checksum,
}) {
final request = put_Request(
localData,
path,
lastModified: lastModified,
created: created,
checksum: checksum,
);

return csrfClient.send(request);
}

/// Request to put a new file at [path] with [localData] as content.
///
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
///
/// See:
/// * [putStream] for a complete operation executing this request.
http.StreamedRequest putStream_Request(
Expand All @@ -145,6 +167,7 @@ class WebDavClient {
required int contentLength,
DateTime? lastModified,
DateTime? created,
String? checksum,
void Function(double progress)? onProgress,
}) {
final request = http.StreamedRequest('PUT', _constructUri(path));
Expand All @@ -155,6 +178,7 @@ class WebDavClient {
lastModified: lastModified,
created: created,
contentLength: contentLength,
checksum: checksum,
);

if (onProgress != null) {
Expand All @@ -181,6 +205,7 @@ class WebDavClient {
/// [lastModified] sets the date when the file was last modified on the server.
/// [created] sets the date when the file was created on the server.
/// [contentLength] sets the length of the [localData] that is uploaded.
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
/// [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.
///
/// See:
Expand All @@ -192,13 +217,15 @@ class WebDavClient {
required int contentLength,
DateTime? lastModified,
DateTime? created,
String? checksum,
void Function(double progress)? onProgress,
}) {
final request = putStream_Request(
localData,
path,
lastModified: lastModified,
created: created,
checksum: checksum,
contentLength: contentLength,
onProgress: onProgress,
);
Expand All @@ -208,6 +235,8 @@ class WebDavClient {

/// Request to put a new file at [path] with [file] as content.
///
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
///
/// See:
/// * [putFile] for a complete operation executing this request.
http.StreamedRequest putFile_Request(
Expand All @@ -216,6 +245,7 @@ class WebDavClient {
PathUri path, {
DateTime? lastModified,
DateTime? created,
String? checksum,
void Function(double progress)? onProgress,
}) {
// Authentication and content-type headers are already set by the putStream_Request.
Expand All @@ -225,6 +255,7 @@ class WebDavClient {
path,
lastModified: lastModified,
created: created,
checksum: checksum,
contentLength: fileStat.size,
onProgress: onProgress,
);
Expand All @@ -234,6 +265,7 @@ class WebDavClient {
///
/// [lastModified] sets the date when the file was last modified on the server.
/// [created] sets the date when the file was created on the server.
/// [checksum] has to follow [checksumPattern]. It will not be validated by the server.
/// [onProgress] can be used to watch the upload progress. Possible values range from 0.0 to 1.0.
/// See:
/// * http://www.webdav.org/specs/rfc2518.html#METHOD_PUT for more information.
Expand All @@ -244,6 +276,7 @@ class WebDavClient {
PathUri path, {
DateTime? lastModified,
DateTime? created,
String? checksum,
void Function(double progress)? onProgress,
}) {
final request = putFile_Request(
Expand All @@ -252,6 +285,7 @@ class WebDavClient {
path,
lastModified: lastModified,
created: created,
checksum: checksum,
onProgress: onProgress,
);

Expand Down Expand Up @@ -571,13 +605,20 @@ class WebDavClient {
required int contentLength,
DateTime? lastModified,
DateTime? created,
String? checksum,
}) {
if (lastModified != null) {
request.headers['X-OC-Mtime'] = lastModified.secondsSinceEpoch.toString();
}
if (created != null) {
request.headers['X-OC-CTime'] = created.secondsSinceEpoch.toString();
}
if (checksum != null) {
if (!checksumPattern.hasMatch(checksum)) {
throw ArgumentError.value(checksum, 'checksum', 'Invalid checksum');
}
request.headers['OC-Checksum'] = checksum;
}
request.headers['content-length'] = contentLength.toString();
}

Expand Down
19 changes: 19 additions & 0 deletions packages/nextcloud/test/api/webdav/webdav_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,25 @@ void main() {
expect(props.ocTags!.tags!.single, 'example');
});

test('Upload file with checksum', () async {
final file = File('test/files/test.png');
await tester.client.webdav.putFile(
file,
file.statSync(),
PathUri.parse('test/checksum.png'),
checksum: 'md5:abc',
);

final response = await tester.client.webdav.propfind(
PathUri.parse('test/checksum.png'),
prop: const WebDavPropWithoutValues.fromBools(
ocChecksums: true,
),
);
final props = response.responses.single.propstats.first.prop;
expect(props.ocChecksums!.checksums!.single, 'md5:abc');
});

test('Upload and download file', () async {
final destinationDir = Directory.systemTemp.createTempSync();
final destination = File('${destinationDir.path}/test.png');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
PUT http://localhost/remote\.php/webdav/test/checksum\.png
authorization: Bearer mock
content-length: 8650
content-type: application/xml
oc-checksum: md5:abc
ocs-apirequest: true
requesttoken: token
.+
PROPFIND http://localhost/remote\.php/webdav/test/checksum\.png
authorization: Bearer mock
content-type: application/xml
ocs-apirequest: true
requesttoken: token
<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 comments on commit 4380f5a

Please sign in to comment.