Skip to content

Commit

Permalink
feat(nextcloud): add http.Client that handles the CSRF token for webd…
Browse files Browse the repository at this point in the history
…av requests

Signed-off-by: Nikolas Rimikis <leptopoda@users.noreply.github.com>
  • Loading branch information
Leptopoda committed Apr 2, 2024
1 parent 20ede06 commit 3e259ba
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 54 deletions.
71 changes: 17 additions & 54 deletions packages/nextcloud/lib/src/webdav/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'dart:typed_data';

import 'package:dynamite_runtime/http_client.dart';
import 'package:http/http.dart' as http;
import 'package:nextcloud/src/webdav/csrf_client.dart';
import 'package:nextcloud/src/webdav/models.dart';
import 'package:nextcloud/src/webdav/path_uri.dart';
import 'package:nextcloud/src/webdav/props.dart';
Expand All @@ -14,55 +15,17 @@ import 'package:nextcloud/src/webdav/webdav.dart';
import 'package:nextcloud/utils.dart';
import 'package:universal_io/io.dart' show File, FileStat;

// ignore: do_not_use_environment
const bool _kIsWeb = bool.fromEnvironment('dart.library.js_util');

/// WebDavClient class
class WebDavClient {
// ignore: public_member_api_docs
WebDavClient(this.rootClient);
WebDavClient(this.rootClient) : csrfClient = WebDavCSRFClient(rootClient);

// ignore: public_member_api_docs
final DynamiteClient rootClient;

String? _token;

Future<http.StreamedResponse> _send(
http.BaseRequest request,
) async {
// On web we need to send a CSRF token because we also send the cookies. In theory this should not be required as
// long as we send the OCS-APIRequest header, but the server has a bug that only triggers when you also send the
// cookies. On non-web platforms we don't send the cookies so we are fine, but on web the browser always does it
// and therefore we need this workaround.
// TODO: Fix this bug in server.
if (_kIsWeb) {
if (_token == null) {
final response = await rootClient.get(Uri.parse('${rootClient.baseURL}/index.php'));
if (response.statusCode >= 300) {
throw DynamiteStatusCodeException(
response.statusCode,
);
}

_token = RegExp('data-requesttoken="([^"]*)"').firstMatch(response.body)!.group(1);
}

request.headers.addAll({
'OCS-APIRequest': 'true',
'requesttoken': _token!,
});
}

final response = await rootClient.send(request);

if (response.statusCode >= 300) {
throw DynamiteStatusCodeException(
response.statusCode,
);
}

return response;
}
/// {@macro WebDavCSRFClient}
// TODO: Fix this bug in server.
final WebDavCSRFClient csrfClient;

Uri _constructUri([PathUri? path]) => constructUri(rootClient.baseURL, path);

Expand All @@ -84,7 +47,7 @@ class WebDavClient {
Future<WebDavOptions> options() async {
final request = options_Request();

final response = await _send(request);
final response = await csrfClient.send(request);
return parseWebDavOptions(response.headers);
}

Expand All @@ -107,7 +70,7 @@ class WebDavClient {
Future<http.StreamedResponse> mkcol(PathUri path) {
final request = mkcol_Request(path);

return _send(request);
return csrfClient.send(request);
}

/// Request to delete the resource at [path].
Expand All @@ -129,7 +92,7 @@ class WebDavClient {
Future<http.StreamedResponse> delete(PathUri path) {
final request = delete_Request(path);

return _send(request);
return csrfClient.send(request);
}

/// Request to put a new file at [path] with [localData] as content.
Expand Down Expand Up @@ -174,7 +137,7 @@ class WebDavClient {
created: created,
);

return _send(request);
return csrfClient.send(request);
}

/// Request to put a new file at [path] with [localData] as content.
Expand Down Expand Up @@ -244,7 +207,7 @@ class WebDavClient {
onProgress: onProgress,
);

return _send(request);
return csrfClient.send(request);
}

/// Request to put a new file at [path] with [file] as content.
Expand Down Expand Up @@ -296,7 +259,7 @@ class WebDavClient {
onProgress: onProgress,
);

return _send(request);
return csrfClient.send(request);
}

/// Request to get the content of the file at [path].
Expand All @@ -320,7 +283,7 @@ class WebDavClient {
}) {
final request = get_Request(path);
// ignore: discarded_futures
final response = _send(request);
final response = csrfClient.send(request);
final controller = StreamController<List<int>>();

unawaited(
Expand Down Expand Up @@ -413,7 +376,7 @@ class WebDavClient {
depth: depth,
);

final response = await _send(request);
final response = await csrfClient.send(request);
return const WebDavResponseConverter().convert(response);
}

Expand Down Expand Up @@ -454,7 +417,7 @@ class WebDavClient {
prop: prop,
);

final response = await _send(request);
final response = await csrfClient.send(request);
return const WebDavResponseConverter().convert(response);
}

Expand Down Expand Up @@ -497,7 +460,7 @@ class WebDavClient {
remove: remove,
);

final response = await _send(request);
final response = await csrfClient.send(request);
final data = await const WebDavResponseConverter().convert(response);
for (final a in data.responses) {
for (final b in a.propstats) {
Expand Down Expand Up @@ -546,7 +509,7 @@ class WebDavClient {
overwrite: overwrite,
);

return _send(request);
return csrfClient.send(request);
}

/// Request to copy the resource from [sourcePath] to [destinationPath].
Expand Down Expand Up @@ -586,7 +549,7 @@ class WebDavClient {
overwrite: overwrite,
);

return _send(request);
return csrfClient.send(request);
}

void _addBaseHeaders(http.BaseRequest request) {
Expand Down
54 changes: 54 additions & 0 deletions packages/nextcloud/lib/src/webdav/csrf_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:dynamite_runtime/http_client.dart';
import 'package:http/http.dart' as http;

// ignore: do_not_use_environment
const bool _kIsWeb = bool.fromEnvironment('dart.library.js_util');

/// A [http.Client] that sends the Nextcloud CSRF token.
///
/// {@template WebDavCSRFClient}
/// On web we need to send a CSRF token because we also send the cookies. In theory this should not be required as
/// long as we send the OCS-APIRequest header, but the server has a bug that only triggers when you also send the
/// cookies. On non-web platforms we don't send the cookies so we are fine, but on web the browser always does it
/// and therefore we need this workaround.
/// {@endtemplate}
final class WebDavCSRFClient with http.BaseClient {
/// Creates a new CSRF client that executes requests through the given [DynamiteClient].
WebDavCSRFClient(this._inner);

final DynamiteClient _inner;

/// The request token sent by the [WebDavCSRFClient].
String? _token;

@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
if (_kIsWeb) {
if (_token == null) {
final response = await _inner.get(Uri.parse('${_inner.baseURL}/index.php'));
if (response.statusCode >= 300) {
throw DynamiteStatusCodeException(
response.statusCode,
);
}

_token = RegExp('data-requesttoken="([^"]*)"').firstMatch(response.body)!.group(1);
}

request.headers.addAll({
'OCS-APIRequest': 'true',
'requesttoken': _token!,
});
}

final response = await _inner.send(request);

if (response.statusCode >= 300) {
throw DynamiteStatusCodeException(
response.statusCode,
);
}

return response;
}
}

0 comments on commit 3e259ba

Please sign in to comment.