LCOV - code coverage report
Current view: top level - lib/src - curt.dart (source / functions) Hit Total Coverage
Test: curt Lines: 50 82 61.0 %
Date: 2024-05-26 14:46:03 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 1.14