diff --git a/.gitignore b/.gitignore
index 8c07133..727319d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,5 +6,7 @@
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
+.idea/
+
build/
coverage/
\ No newline at end of file
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
deleted file mode 100644
index 8f6c046..0000000
--- a/.idea/libraries/Dart_Packages.xml
+++ /dev/null
@@ -1,508 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml
deleted file mode 100644
index f4e39df..0000000
--- a/.idea/libraries/Dart_SDK.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 639900d..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 1cdcf8d..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.pubignore b/.pubignore
new file mode 100644
index 0000000..69b2ab9
--- /dev/null
+++ b/.pubignore
@@ -0,0 +1,5 @@
+.github/
+helpers/
+coverage/
+build/
+*.tar.gz
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6f387a..a1a1b3a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 0.1.0 (2023-06-21)
+
+- Adding CurtHttpHeaders class.
+- Adding CurtResponse class.
+- Updating tests.
+- Thanks [GThurler](https://github.com/GThurler) for the improvements.
+
## 0.0.3 (2023-06-20)
- Cleaning headers when follow redirect is active.
diff --git a/README.md b/README.md
index b5f9e43..a36af4f 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Simple wrapper for curl in dart.
## Motivation
-Allow https connections with TLS lower than 1.2.
+Allow https connections with TLS less than 1.2 through curl.
## Funding
diff --git a/example/curt_example.dart b/example/curt_example.dart
index 81da86d..47cb68b 100644
--- a/example/curt_example.dart
+++ b/example/curt_example.dart
@@ -1,14 +1,13 @@
// ignore_for_file: avoid_print
import 'package:curt/curt.dart';
-import 'package:http/http.dart';
///
///
///
void main() async {
final Curt curt = Curt();
- final Response response = await curt.get(Uri.parse('https://google.com'));
+ final CurtResponse response = await curt.get(Uri.parse('https://google.com'));
print('Status Code: ${response.statusCode}');
print('Headers: ${response.headers}');
print('Body:\n${response.body}');
diff --git a/lib/curt.dart b/lib/curt.dart
index fb0f091..82048a1 100644
--- a/lib/curt.dart
+++ b/lib/curt.dart
@@ -2,3 +2,4 @@
library curt;
export 'src/curt.dart';
+export 'src/curt_response.dart';
diff --git a/lib/src/curt.dart b/lib/src/curt.dart
index a2490e4..f0dc5b8 100644
--- a/lib/src/curt.dart
+++ b/lib/src/curt.dart
@@ -3,7 +3,8 @@
import 'dart:convert';
import 'dart:io';
-import 'package:http/http.dart';
+import 'package:curt/src/curt_http_headers.dart';
+import 'package:curt/src/curt_response.dart';
///
///
@@ -31,10 +32,11 @@ class Curt {
///
///
///
- Future send(
+ Future send(
Uri uri, {
required String method,
Map headers = const {},
+ List cookies = const [],
String? data,
}) async {
final List args = ['-v', '-X', method];
@@ -61,6 +63,13 @@ class Curt {
..add('${header.key}: ${header.value}');
}
+ /// Cookies
+ for (final Cookie cookie in cookies) {
+ args
+ ..add('--cookie')
+ ..add('${cookie.name}=${cookie.value}');
+ }
+
/// Body data
if (data != null) {
args
@@ -104,7 +113,7 @@ class Curt {
int statusCode = -1;
- final Map responseHeaders = {};
+ final CurtHttpHeaders responseHeaders = CurtHttpHeaders();
for (final String verboseLine in verboseLines) {
if (debug) {
@@ -120,8 +129,10 @@ class Curt {
RegExpMatch? match = headerRegExp.firstMatch(line);
if (match != null) {
- responseHeaders[match.namedGroup('key').toString()] =
- match.namedGroup('value').toString();
+ responseHeaders.add(
+ match.namedGroup('key').toString(),
+ match.namedGroup('value').toString(),
+ );
continue;
}
@@ -134,7 +145,7 @@ class Curt {
}
}
- return Response(
+ return CurtResponse(
run.stdout.toString(),
statusCode,
headers: responseHeaders,
@@ -144,19 +155,22 @@ class Curt {
///
///
///
- Future sendJson(
+ Future sendJson(
Uri uri, {
required String method,
required Map body,
Map headers = const {},
+ List cookies = const [],
+ String contentType = 'application/json',
}) {
final Map newHeaders = Map.of(headers);
- newHeaders['Content-Type'] = 'application/json';
+ newHeaders['Content-Type'] = contentType;
return send(
uri,
method: method,
headers: newHeaders,
+ cookies: cookies,
data: json.encode(body),
);
}
@@ -164,58 +178,80 @@ class Curt {
///
///
///
- Future get(
+ Future get(
Uri uri, {
Map headers = const {},
+ List cookies = const [],
}) =>
- send(uri, method: 'GET', headers: headers);
+ send(uri, method: 'GET', headers: headers, cookies: cookies);
///
///
///
- Future post(
+ Future post(
Uri uri, {
Map headers = const {},
+ List cookies = const [],
String? data,
}) =>
- send(uri, method: 'POST', headers: headers, data: data);
+ send(uri, method: 'POST', headers: headers, data: data, cookies: cookies);
///
///
///
- Future postJson(
+ Future postJson(
Uri uri, {
required Map body,
Map headers = const {},
+ List cookies = const [],
+ String contentType = 'application/json',
}) =>
- sendJson(uri, method: 'POST', headers: headers, body: body);
+ sendJson(
+ uri,
+ method: 'POST',
+ headers: headers,
+ body: body,
+ cookies: cookies,
+ contentType: contentType,
+ );
///
///
///
- Future put(
+ Future put(
Uri uri, {
Map headers = const {},
+ List cookies = const [],
String? data,
}) =>
- send(uri, method: 'PUT', headers: headers, data: data);
+ send(uri, method: 'PUT', headers: headers, data: data, cookies: cookies);
///
///
///
- Future putJson(
+ Future putJson(
Uri uri, {
required Map body,
Map headers = const {},
+ List cookies = const [],
+ String contentType = 'application/json',
}) =>
- sendJson(uri, method: 'PUT', headers: headers, body: body);
+ sendJson(
+ uri,
+ method: 'PUT',
+ headers: headers,
+ body: body,
+ cookies: cookies,
+ contentType: contentType,
+ );
///
///
///
- Future delete(
+ Future delete(
Uri uri, {
Map headers = const {},
+ List cookies = const [],
}) =>
- send(uri, method: 'DELETE', headers: headers);
+ send(uri, method: 'DELETE', headers: headers, cookies: cookies);
}
diff --git a/lib/src/curt_http_headers.dart b/lib/src/curt_http_headers.dart
new file mode 100644
index 0000000..972d273
--- /dev/null
+++ b/lib/src/curt_http_headers.dart
@@ -0,0 +1,211 @@
+import 'dart:io';
+
+import 'package:intl/intl.dart';
+
+///
+///
+///
+class CurtHttpHeaders implements HttpHeaders {
+ final Map> _headers = >{};
+
+ // TODO(anyone): properly implement these stubbed properties and methods?
+ /// All of these go unused for our current use cases, so they're being stubbed
+ @override
+ bool chunkedTransferEncoding = true;
+
+ @override
+ int contentLength = -1;
+
+ @override
+ ContentType? contentType;
+
+ @override
+ DateTime? date;
+
+ @override
+ DateTime? expires;
+
+ @override
+ String? host;
+
+ @override
+ DateTime? ifModifiedSince;
+
+ @override
+ bool persistentConnection = true;
+
+ @override
+ int? port;
+
+ ///
+ ///
+ ///
+ @override
+ void noFolding(String name) {}
+
+ ///
+ ///
+ ///
+ void _add(String name, String value) {
+ final String lowerName = name.toLowerCase();
+
+ switch (lowerName) {
+ case HttpHeaders.contentLengthHeader:
+ contentLength = int.tryParse(value) ?? -1;
+ break;
+ case HttpHeaders.dateHeader:
+ try {
+ date = DateFormat('EEE, dd MMM yyyy HH:mm:ss zzz').parse(value);
+ } on Exception catch (_) {}
+ break;
+ }
+
+ if (_headers.containsKey(lowerName)) {
+ if (!_headers[lowerName]!.contains(value)) {
+ _headers[lowerName]!.add(value);
+ }
+ } else {
+ _headers[lowerName] = [value];
+ }
+ }
+
+ ///
+ ///
+ ///
+ void _remove(String name, String value) {
+ final String lowerName = name.toLowerCase();
+ if (_headers.containsKey(lowerName)) {
+ _headers[lowerName]!.remove(value);
+ if (_headers[lowerName]!.isEmpty) {
+ _headers.remove(lowerName);
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ void _addAll(String name, Iterable values) {
+ for (final String value in values) {
+ _add(name, value);
+ }
+ }
+
+ ///
+ ///
+ ///
+ void _removeAll(String name, Iterable values) {
+ for (final String value in values) {
+ _remove(name, value);
+ }
+ }
+
+ ///
+ ///
+ ///
+ @override
+ void set(String name, Object value, {bool preserveHeaderCase = false}) {
+ final String nameToUse = preserveHeaderCase ? name : name.toLowerCase();
+ if (value is Iterable) {
+ _headers[nameToUse] = value.map((dynamic v) => v.toString()).toList();
+ } else {
+ _headers[nameToUse] = [value.toString()];
+ }
+ }
+
+ ///
+ ///
+ ///
+ @override
+ void add(String name, Object value, {bool preserveHeaderCase = false}) {
+ final String nameToUse = preserveHeaderCase ? name : name.toLowerCase();
+ if (value is Iterable) {
+ _addAll(nameToUse, value.map((dynamic v) => v.toString()));
+ } else {
+ _add(nameToUse, value.toString());
+ }
+ }
+
+ ///
+ ///
+ ///
+ @override
+ void clear() => _headers.clear();
+
+ ///
+ ///
+ ///
+ @override
+ void remove(String name, Object value) {
+ if (value is Iterable) {
+ _removeAll(name, value.map((dynamic v) => v.toString()));
+ } else {
+ _remove(name, value.toString());
+ }
+ }
+
+ ///
+ ///
+ ///
+ @override
+ void removeAll(String name) => _headers.remove(name.toLowerCase());
+
+ ///
+ ///
+ ///
+ @override
+ void forEach(void Function(String name, List values) action) =>
+ _headers.forEach(action);
+
+ ///
+ ///
+ ///
+ @override
+ String? value(String name) {
+ final String lowerName = name.toLowerCase();
+ if (_headers.containsKey(lowerName) && _headers[lowerName]!.isNotEmpty) {
+ return _headers[lowerName]!.first;
+ }
+ return null;
+ }
+
+ ///
+ ///
+ ///
+ @override
+ List? operator [](String name) => _headers[name.toLowerCase()];
+
+ ///
+ ///
+ ///
+ @override
+ String toString() => _headers.toString();
+
+ ///
+ ///
+ ///
+ bool get isEmpty => _headers.isEmpty;
+
+ ///
+ ///
+ ///
+ bool get isNotEmpty => _headers.isNotEmpty;
+
+ ///
+ ///
+ ///
+ List get cookies {
+ if (!_headers.containsKey(HttpHeaders.setCookieHeader)) {
+ return [];
+ }
+ final Map rawInfo = {};
+ for (final String rawValue in _headers[HttpHeaders.setCookieHeader]!) {
+ final Cookie cookie = Cookie.fromSetCookieValue(rawValue);
+ rawInfo[cookie.name] = cookie.value; // This prevents duplicate cookies
+ }
+ return rawInfo.entries
+ .map((MapEntry entry) => Cookie(entry.key, entry.value))
+ .toList();
+ }
+
+}
diff --git a/lib/src/curt_response.dart b/lib/src/curt_response.dart
new file mode 100644
index 0000000..2f8a566
--- /dev/null
+++ b/lib/src/curt_response.dart
@@ -0,0 +1,21 @@
+import 'dart:io';
+import 'package:curt/src/curt_http_headers.dart';
+
+///
+///
+///
+class CurtResponse {
+ final String body;
+ final int statusCode;
+ final CurtHttpHeaders headers;
+ final List cookies;
+
+ ///
+ ///
+ ///
+ CurtResponse(
+ this.body,
+ this.statusCode, {
+ required this.headers,
+ }) : cookies = headers.cookies;
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index d737fb9..a5fe45f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: curt
description: Simple wrapper for curl in dart.
-version: 0.0.3
+version: 0.1.0
repository: https://github.com/edufolly/curt
environment:
@@ -9,6 +9,8 @@ environment:
dependencies:
http: '>=0.13.5 <1.1.0'
+ intl: '>=0.17.0 <1.0.0'
+
dev_dependencies:
# https://pub.dev/packages/coverage
coverage: ^1.6.3
diff --git a/test/curt_test.dart b/test/curt_test.dart
index b7b14b5..197ed9c 100644
--- a/test/curt_test.dart
+++ b/test/curt_test.dart
@@ -1,10 +1,8 @@
-// ignore_for_file: format-comment
-
import 'dart:io';
import 'dart:math';
import 'package:curt/src/curt.dart';
-import 'package:http/http.dart';
+import 'package:curt/src/curt_response.dart';
import 'package:test/test.dart';
import '../helpers/basic_test_result.dart';
@@ -77,28 +75,28 @@ void main() {
for (final MapEntry entry in tests.entries) {
test('GET ${entry.key}', () async {
- final Response response = await curt.get(Uri.parse(entry.key));
+ final CurtResponse response = await curt.get(Uri.parse(entry.key));
expect(response.statusCode, entry.value.statusCode);
expect(response.headers, entry.value.headersMatcher);
expect(response.body, entry.value.bodyMatcher);
});
test('POST ${entry.key}', () async {
- final Response response = await curt.post(Uri.parse(entry.key));
+ final CurtResponse response = await curt.post(Uri.parse(entry.key));
expect(response.statusCode, entry.value.statusCode);
expect(response.headers, entry.value.headersMatcher);
expect(response.body, entry.value.bodyMatcher);
});
test('PUT ${entry.key}', () async {
- final Response response = await curt.put(Uri.parse(entry.key));
+ final CurtResponse response = await curt.put(Uri.parse(entry.key));
expect(response.statusCode, entry.value.statusCode);
expect(response.headers, entry.value.headersMatcher);
expect(response.body, entry.value.bodyMatcher);
});
test('DELETE ${entry.key}', () async {
- final Response response = await curt.delete(Uri.parse(entry.key));
+ final CurtResponse response = await curt.delete(Uri.parse(entry.key));
expect(response.statusCode, entry.value.statusCode);
expect(response.headers, entry.value.headersMatcher);
expect(response.body, entry.value.bodyMatcher);
@@ -108,17 +106,13 @@ void main() {
for (int gen = 0; gen < 3; gen++) {
final int bytes = Random().nextInt(1024);
test('Body Length $bytes', () async {
- final Response response = await curt.get(
+ final CurtResponse response = await curt.get(
Uri.parse('$scheme://$server:$httpPort/range/$bytes'),
);
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
- expect(response.headers.containsKey('Content-Length'), isTrue);
- expect(
- int.tryParse(response.headers['Content-Length'].toString()) ?? -1,
- bytes,
- );
+ expect(response.headers.contentLength, bytes);
expect(response.body, isNotEmpty);
expect(response.body.length, bytes);
});
@@ -137,11 +131,11 @@ void main() {
},
);
- final Response response = await curt.get(uri);
+ final CurtResponse response = await curt.get(uri);
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
- expect(response.headers.containsKey('location'), isFalse);
+ expect(response.headers.value(HttpHeaders.locationHeader), isNull);
expect(response.body, isEmpty);
});
@@ -205,7 +199,7 @@ void main() {
///
test('Simple HTTP GET', () async {
- final Response response =
+ final CurtResponse response =
await curt.get(Uri.parse('http://$server:$httpPort/'));
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
@@ -214,7 +208,7 @@ void main() {
///
test('Simple HTTP POST', () async {
- final Response response =
+ final CurtResponse response =
await curt.post(Uri.parse('http://$server:$httpPort/'));
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
@@ -223,7 +217,7 @@ void main() {
///
test('Simple HTTP PUT', () async {
- final Response response =
+ final CurtResponse response =
await curt.put(Uri.parse('http://$server:$httpPort/'));
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
@@ -232,7 +226,7 @@ void main() {
///
test('Simple HTTP DELETE', () async {
- final Response response =
+ final CurtResponse response =
await curt.delete(Uri.parse('http://$server:$httpPort/'));
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
@@ -241,7 +235,7 @@ void main() {
///
test('Simple HTTPS GET', () async {
- final Response response =
+ final CurtResponse response =
await curt.get(Uri.parse('https://$server:$httpsPort/'));
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
@@ -250,7 +244,7 @@ void main() {
///
test('Simple HTTPS POST', () async {
- final Response response =
+ final CurtResponse response =
await curt.post(Uri.parse('https://$server:$httpsPort/'));
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
@@ -259,7 +253,7 @@ void main() {
///
test('Simple HTTPS PUT', () async {
- final Response response =
+ final CurtResponse response =
await curt.put(Uri.parse('https://$server:$httpsPort/'));
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);
@@ -268,7 +262,7 @@ void main() {
///
test('Simple HTTPS DELETE', () async {
- final Response response =
+ final CurtResponse response =
await curt.delete(Uri.parse('https://$server:$httpsPort/'));
expect(response.statusCode, 200);
expect(response.headers, isNotEmpty);