LCOV - code coverage report
Current view: top level - lib/src - curt.dart (source / functions) Coverage Total Hit
Test: curt Lines: 61.0 % 82 50
Test Date: 2025-02-19 14:03:45 Functions: - 0 0

            Line data    Source code
       1              : // ignore_for_file: avoid_print
       2              : 
       3              : import 'dart:convert';
       4              : import 'dart:io';
       5              : 
       6              : import 'package:curt/src/curt_http_headers.dart';
       7              : import 'package:curt/src/curt_response.dart';
       8              : 
       9              : ///
      10              : ///
      11              : ///
      12              : class Curt {
      13              :   static const String opensslConfigOverridePath = '/tmp/curt-openssl.cnf';
      14              : 
      15              :   final Map<String, String> environment = <String, String>{};
      16              :   final String executable;
      17              :   final bool debug;
      18              :   final bool insecure;
      19              :   final bool silent;
      20              :   final bool followRedirects;
      21              :   final bool linuxOpensslTLSOverride;
      22              :   final int timeout;
      23              : 
      24              :   ///
      25              :   ///
      26              :   ///
      27            1 :   Curt({
      28              :     this.executable = 'curl',
      29              :     this.debug = false,
      30              :     this.insecure = false,
      31              :     this.silent = true,
      32              :     this.followRedirects = false,
      33              :     this.linuxOpensslTLSOverride = false,
      34              :     this.timeout = 10000,
      35              :   }) {
      36              :     /// https://askubuntu.com/questions/1250787/when-i-try-to-curl-a-website-i-get-ssl-error
      37              :     /// This openssl problem can happen on any distro with curl linking
      38              :     /// dynamically to openssl, even though the link is specific to ubuntu.
      39              :     /// The workaround here is to create a config override for openssl, and run
      40              :     /// curl with that config override - this will allow TLS v1.0 and v1.1
      41              :     /// requests to work, which are blocked by openssl, NOT curl
      42            2 :     if (Platform.isLinux && linuxOpensslTLSOverride) {
      43            0 :       final StringBuffer buffer = StringBuffer()
      44            0 :         ..writeln('openssl_conf = openssl_init')
      45            0 :         ..writeln('[openssl_init]')
      46            0 :         ..writeln('ssl_conf = ssl_sect')
      47            0 :         ..writeln('[ssl_sect]')
      48            0 :         ..writeln('system_default = system_default_sect')
      49            0 :         ..writeln('[system_default_sect]')
      50            0 :         ..writeln('CipherString = DEFAULT@SECLEVEL=1');
      51              : 
      52            0 :       File(opensslConfigOverridePath)
      53            0 :         ..createSync(recursive: true)
      54            0 :         ..writeAsStringSync(buffer.toString());
      55              : 
      56            0 :       environment['OPENSSL_CONF'] = opensslConfigOverridePath;
      57              :     }
      58              :   }
      59              : 
      60              :   ///
      61              :   ///
      62              :   ///
      63            1 :   Future<CurtResponse> send(
      64              :     Uri uri, {
      65              :     required String method,
      66              :     Map<String, String> headers = const <String, String>{},
      67              :     List<Cookie> cookies = const <Cookie>[],
      68              :     String? data,
      69              :   }) async {
      70            1 :     final List<String> args = <String>['-v', '-X', method];
      71              : 
      72              :     /// Insecure
      73            1 :     if (insecure) {
      74            1 :       args.add('-k');
      75              :     }
      76              : 
      77              :     /// Silent
      78            1 :     if (silent) {
      79            1 :       args.add('-s');
      80              :     }
      81              : 
      82              :     /// Follow Redirects
      83            1 :     if (followRedirects) {
      84            1 :       args.add('-L');
      85              :     }
      86              : 
      87              :     /// Headers
      88            2 :     for (final MapEntry<String, String> header in headers.entries) {
      89              :       args
      90            1 :         ..add('-H')
      91            4 :         ..add('${header.key}: ${header.value}');
      92              :     }
      93              : 
      94              :     /// Cookies
      95            1 :     for (final Cookie cookie in cookies) {
      96              :       args
      97            0 :         ..add('--cookie')
      98            0 :         ..add('${cookie.name}=${cookie.value}');
      99              :     }
     100              : 
     101              :     /// Body data
     102              :     if (data != null) {
     103              :       args
     104            0 :         ..add('-d')
     105            0 :         ..add(data);
     106              :     }
     107              : 
     108              :     /// URL
     109            2 :     args.add(uri.toString());
     110              : 
     111            1 :     if (debug) {
     112            0 :       print('$executable ${args.join(' ')}');
     113              :     }
     114              : 
     115              :     ///
     116              :     /// Run
     117              :     ///
     118            1 :     final ProcessResult run = await Process.run(
     119            1 :       executable,
     120              :       args,
     121            1 :       environment: environment,
     122            1 :     ).timeout(
     123            1 :       Duration(
     124            1 :         milliseconds: timeout,
     125              :       ),
     126              :     );
     127              : 
     128            2 :     if (run.exitCode != 0) {
     129            0 :       if (debug) {
     130            0 :         print('Exit Code: ${run.exitCode}');
     131            0 :         print(run.stdout);
     132            0 :         print(run.stderr);
     133              :       }
     134            0 :       throw Exception('Error: ${run.exitCode} - ${run.stderr}');
     135              :     }
     136              : 
     137              :     ///
     138              :     /// Parse
     139              :     ///
     140            3 :     final List<String> verboseLines = run.stderr.toString().split('\n');
     141              : 
     142            1 :     final RegExp headerRegExp = RegExp('(?<key>.*?): (?<value>.*)');
     143              : 
     144            1 :     final RegExp protocolRegExp = RegExp(r'HTTP(.*?) (?<statusCode>\d*)');
     145              : 
     146            1 :     int statusCode = -1;
     147              : 
     148            1 :     final CurtHttpHeaders responseHeaders = CurtHttpHeaders();
     149              : 
     150            2 :     for (final String verboseLine in verboseLines) {
     151            1 :       if (debug) {
     152            0 :         print(verboseLine);
     153              :       }
     154              : 
     155            1 :       if (verboseLine.isEmpty) {
     156              :         continue;
     157              :       }
     158              : 
     159            2 :       if (verboseLine.substring(0, 1) == '<') {
     160            1 :         final String line = verboseLine.substring(2);
     161              : 
     162            1 :         RegExpMatch? match = headerRegExp.firstMatch(line);
     163              :         if (match != null) {
     164            1 :           responseHeaders.add(
     165            2 :             match.namedGroup('key').toString(),
     166            2 :             match.namedGroup('value').toString(),
     167              :           );
     168              :           continue;
     169              :         }
     170              : 
     171            1 :         match = protocolRegExp.firstMatch(line);
     172              :         if (match != null) {
     173              :           statusCode =
     174            3 :               int.tryParse(match.namedGroup('statusCode').toString()) ?? -1;
     175            1 :           responseHeaders.clear();
     176              :         }
     177              :       }
     178              :     }
     179              : 
     180            1 :     return CurtResponse(
     181            2 :       run.stdout.toString(),
     182              :       statusCode,
     183              :       headers: responseHeaders,
     184              :     );
     185              :   }
     186              : 
     187              :   ///
     188              :   ///
     189              :   ///
     190            0 :   Future<CurtResponse> sendJson(
     191              :     Uri uri, {
     192              :     required String method,
     193              :     required Map<String, dynamic> body,
     194              :     Map<String, String> headers = const <String, String>{},
     195              :     List<Cookie> cookies = const <Cookie>[],
     196              :     String contentType = 'application/json',
     197              :   }) {
     198            0 :     final Map<String, String> newHeaders = Map<String, String>.of(headers);
     199            0 :     newHeaders['Content-Type'] = contentType;
     200              : 
     201            0 :     return send(
     202              :       uri,
     203              :       method: method,
     204              :       headers: newHeaders,
     205              :       cookies: cookies,
     206            0 :       data: json.encode(body),
     207              :     );
     208              :   }
     209              : 
     210              :   ///
     211              :   ///
     212              :   ///
     213            1 :   Future<CurtResponse> get(
     214              :     Uri uri, {
     215              :     Map<String, String> headers = const <String, String>{},
     216              :     List<Cookie> cookies = const <Cookie>[],
     217              :   }) =>
     218            1 :       send(uri, method: 'GET', headers: headers, cookies: cookies);
     219              : 
     220              :   ///
     221              :   ///
     222              :   ///
     223            1 :   Future<CurtResponse> post(
     224              :     Uri uri, {
     225              :     Map<String, String> headers = const <String, String>{},
     226              :     List<Cookie> cookies = const <Cookie>[],
     227              :     String? data,
     228              :   }) =>
     229            1 :       send(uri, method: 'POST', headers: headers, data: data, cookies: cookies);
     230              : 
     231              :   ///
     232              :   ///
     233              :   ///
     234            0 :   Future<CurtResponse> postJson(
     235              :     Uri uri, {
     236              :     required Map<String, dynamic> body,
     237              :     Map<String, String> headers = const <String, String>{},
     238              :     List<Cookie> cookies = const <Cookie>[],
     239              :     String contentType = 'application/json',
     240              :   }) =>
     241            0 :       sendJson(
     242              :         uri,
     243              :         method: 'POST',
     244              :         headers: headers,
     245              :         body: body,
     246              :         cookies: cookies,
     247              :         contentType: contentType,
     248              :       );
     249              : 
     250              :   ///
     251              :   ///
     252              :   ///
     253            1 :   Future<CurtResponse> put(
     254              :     Uri uri, {
     255              :     Map<String, String> headers = const <String, String>{},
     256              :     List<Cookie> cookies = const <Cookie>[],
     257              :     String? data,
     258              :   }) =>
     259            1 :       send(uri, method: 'PUT', headers: headers, data: data, cookies: cookies);
     260              : 
     261              :   ///
     262              :   ///
     263              :   ///
     264            0 :   Future<CurtResponse> putJson(
     265              :     Uri uri, {
     266              :     required Map<String, dynamic> body,
     267              :     Map<String, String> headers = const <String, String>{},
     268              :     List<Cookie> cookies = const <Cookie>[],
     269              :     String contentType = 'application/json',
     270              :   }) =>
     271            0 :       sendJson(
     272              :         uri,
     273              :         method: 'PUT',
     274              :         headers: headers,
     275              :         body: body,
     276              :         cookies: cookies,
     277              :         contentType: contentType,
     278              :       );
     279              : 
     280              :   ///
     281              :   ///
     282              :   ///
     283            1 :   Future<CurtResponse> delete(
     284              :     Uri uri, {
     285              :     Map<String, String> headers = const <String, String>{},
     286              :     List<Cookie> cookies = const <Cookie>[],
     287              :   }) =>
     288            1 :       send(uri, method: 'DELETE', headers: headers, cookies: cookies);
     289              : }
        

Generated by: LCOV version 2.0-1