@platformatic/rdkafka 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/README.md +5 -5
  2. package/bench/seed.sh +0 -0
  3. package/binding.gyp +8 -1
  4. package/ci/prepublish.js +0 -1
  5. package/config.d.ts +126 -10
  6. package/configure +0 -0
  7. package/deps/librdkafka/.semaphore/run-all-tests.yml +77 -0
  8. package/deps/librdkafka/.semaphore/semaphore-integration.yml +250 -0
  9. package/deps/librdkafka/.semaphore/semaphore.yml +67 -44
  10. package/deps/librdkafka/.semaphore/verify-linux-packages.yml +41 -0
  11. package/deps/librdkafka/CHANGELOG.md +471 -1
  12. package/deps/librdkafka/CONFIGURATION.md +29 -9
  13. package/deps/librdkafka/INTRODUCTION.md +909 -601
  14. package/deps/librdkafka/LICENSES.txt +592 -1
  15. package/deps/librdkafka/Makefile +2 -1
  16. package/deps/librdkafka/_mkltmpuuWEwa.c +13 -0
  17. package/deps/librdkafka/configure +0 -0
  18. package/deps/librdkafka/debian/rules +0 -0
  19. package/deps/librdkafka/dev-conf.sh +0 -0
  20. package/deps/librdkafka/examples/consumer.c +18 -8
  21. package/deps/librdkafka/examples/describe_consumer_groups.c +25 -6
  22. package/deps/librdkafka/examples/incremental_alter_configs.c +4 -5
  23. package/deps/librdkafka/examples/kafkatest_verifiable_client.cpp +26 -42
  24. package/deps/librdkafka/examples/openssl_engine_example.cpp +3 -4
  25. package/deps/librdkafka/examples/producer.cpp +2 -2
  26. package/deps/librdkafka/examples/user_scram.c +4 -5
  27. package/deps/librdkafka/examples/win_ssl_cert_store.cpp +4 -3
  28. package/deps/librdkafka/lds-gen.py +0 -0
  29. package/deps/librdkafka/mklove/Makefile.base +0 -0
  30. package/deps/librdkafka/packaging/RELEASE.md +8 -0
  31. package/deps/librdkafka/packaging/alpine/build-alpine.sh +0 -0
  32. package/deps/librdkafka/packaging/cp/README.md +4 -1
  33. package/deps/librdkafka/packaging/cp/check_features.c +12 -4
  34. package/deps/librdkafka/packaging/cp/verify-deb.sh +6 -6
  35. package/deps/librdkafka/packaging/cp/verify-packages.sh +52 -31
  36. package/deps/librdkafka/packaging/cp/verify-rpm.sh +6 -13
  37. package/deps/librdkafka/packaging/debian/rules +0 -0
  38. package/deps/librdkafka/packaging/get_version.py +0 -0
  39. package/deps/librdkafka/packaging/homebrew/brew-update-pr.sh +0 -0
  40. package/deps/librdkafka/packaging/mingw-w64/{travis-before-install.sh → export-variables.sh} +0 -7
  41. package/deps/librdkafka/packaging/mingw-w64/semaphoreci-build.sh +1 -1
  42. package/deps/librdkafka/packaging/nuget/artifact.py +0 -0
  43. package/deps/librdkafka/packaging/nuget/cleanup-s3.py +0 -0
  44. package/deps/librdkafka/packaging/nuget/nuget.sh +0 -0
  45. package/deps/librdkafka/packaging/nuget/packaging.py +0 -0
  46. package/deps/librdkafka/packaging/nuget/push-to-nuget.sh +0 -0
  47. package/deps/librdkafka/packaging/nuget/release.py +0 -0
  48. package/deps/librdkafka/packaging/nuget/requirements.txt +3 -3
  49. package/deps/librdkafka/packaging/rpm/mock-on-docker.sh +0 -0
  50. package/deps/librdkafka/packaging/rpm/tests/run-test.sh +0 -0
  51. package/deps/librdkafka/packaging/rpm/tests/test-on-docker.sh +0 -0
  52. package/deps/librdkafka/packaging/tools/build-configurations-checks.sh +3 -1
  53. package/deps/librdkafka/packaging/tools/build-deb-package.sh +0 -0
  54. package/deps/librdkafka/packaging/tools/build-debian.sh +0 -0
  55. package/deps/librdkafka/packaging/tools/build-manylinux.sh +0 -0
  56. package/deps/librdkafka/packaging/tools/build-release-artifacts.sh +0 -0
  57. package/deps/librdkafka/packaging/tools/distro-build.sh +0 -0
  58. package/deps/librdkafka/packaging/tools/gh-release-checksums.py +0 -0
  59. package/deps/librdkafka/packaging/tools/rdutcoverage.sh +0 -0
  60. package/deps/librdkafka/packaging/tools/run-in-docker.sh +28 -0
  61. package/deps/librdkafka/packaging/tools/run-integration-tests.sh +31 -0
  62. package/deps/librdkafka/packaging/tools/run-style-check.sh +4 -0
  63. package/deps/librdkafka/packaging/tools/style-format.sh +6 -5
  64. package/deps/librdkafka/packaging/tools/update_rpcs_max_versions.py +100 -0
  65. package/deps/librdkafka/service.yml +154 -0
  66. package/deps/librdkafka/src/generate_proto.sh +0 -0
  67. package/deps/librdkafka/src/rd.h +145 -2
  68. package/deps/librdkafka/src/rdaddr.h +6 -7
  69. package/deps/librdkafka/src/rdatomic.h +15 -11
  70. package/deps/librdkafka/src/rdavg.h +3 -2
  71. package/deps/librdkafka/src/rdbase64.c +33 -2
  72. package/deps/librdkafka/src/rdbase64.h +2 -0
  73. package/deps/librdkafka/src/rdendian.h +10 -9
  74. package/deps/librdkafka/src/rdhttp.c +418 -100
  75. package/deps/librdkafka/src/rdhttp.h +23 -5
  76. package/deps/librdkafka/src/rdkafka.c +299 -145
  77. package/deps/librdkafka/src/rdkafka.h +89 -6
  78. package/deps/librdkafka/src/rdkafka_admin.c +482 -105
  79. package/deps/librdkafka/src/rdkafka_admin.h +42 -0
  80. package/deps/librdkafka/src/rdkafka_broker.c +463 -312
  81. package/deps/librdkafka/src/rdkafka_broker.h +68 -10
  82. package/deps/librdkafka/src/rdkafka_buf.c +8 -5
  83. package/deps/librdkafka/src/rdkafka_buf.h +4 -3
  84. package/deps/librdkafka/src/rdkafka_cert.c +25 -1
  85. package/deps/librdkafka/src/rdkafka_cert.h +4 -3
  86. package/deps/librdkafka/src/rdkafka_cgrp.c +468 -143
  87. package/deps/librdkafka/src/rdkafka_cgrp.h +36 -2
  88. package/deps/librdkafka/src/rdkafka_conf.c +526 -102
  89. package/deps/librdkafka/src/rdkafka_conf.h +68 -4
  90. package/deps/librdkafka/src/rdkafka_feature.c +472 -35
  91. package/deps/librdkafka/src/rdkafka_feature.h +2 -0
  92. package/deps/librdkafka/src/rdkafka_fetcher.c +22 -2
  93. package/deps/librdkafka/src/rdkafka_fetcher.h +2 -2
  94. package/deps/librdkafka/src/rdkafka_int.h +84 -46
  95. package/deps/librdkafka/src/rdkafka_metadata.c +221 -136
  96. package/deps/librdkafka/src/rdkafka_metadata.h +15 -11
  97. package/deps/librdkafka/src/rdkafka_metadata_cache.c +279 -73
  98. package/deps/librdkafka/src/rdkafka_mock.c +795 -19
  99. package/deps/librdkafka/src/rdkafka_mock.h +119 -0
  100. package/deps/librdkafka/src/rdkafka_mock_cgrp.c +1298 -132
  101. package/deps/librdkafka/src/rdkafka_mock_handlers.c +345 -49
  102. package/deps/librdkafka/src/rdkafka_mock_int.h +180 -61
  103. package/deps/librdkafka/src/rdkafka_msg.c +35 -19
  104. package/deps/librdkafka/src/rdkafka_msg.h +3 -3
  105. package/deps/librdkafka/src/rdkafka_msgset_writer.c +14 -10
  106. package/deps/librdkafka/src/rdkafka_offset.c +33 -5
  107. package/deps/librdkafka/src/rdkafka_op.c +1 -1
  108. package/deps/librdkafka/src/rdkafka_op.h +45 -21
  109. package/deps/librdkafka/src/rdkafka_partition.c +118 -23
  110. package/deps/librdkafka/src/rdkafka_partition.h +16 -7
  111. package/deps/librdkafka/src/rdkafka_proto.h +4 -1
  112. package/deps/librdkafka/src/rdkafka_protocol.h +1 -0
  113. package/deps/librdkafka/src/rdkafka_queue.c +125 -48
  114. package/deps/librdkafka/src/rdkafka_queue.h +19 -0
  115. package/deps/librdkafka/src/rdkafka_range_assignor.c +1 -1
  116. package/deps/librdkafka/src/rdkafka_request.c +336 -64
  117. package/deps/librdkafka/src/rdkafka_request.h +24 -1
  118. package/deps/librdkafka/src/rdkafka_sasl.c +5 -3
  119. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer.c +14 -10
  120. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.c +1166 -89
  121. package/deps/librdkafka/src/rdkafka_sasl_oauthbearer_oidc.h +13 -3
  122. package/deps/librdkafka/src/rdkafka_sasl_plain.c +3 -3
  123. package/deps/librdkafka/src/rdkafka_sasl_scram.c +4 -3
  124. package/deps/librdkafka/src/rdkafka_ssl.c +306 -81
  125. package/deps/librdkafka/src/rdkafka_ssl.h +22 -0
  126. package/deps/librdkafka/src/rdkafka_sticky_assignor.c +4 -0
  127. package/deps/librdkafka/src/rdkafka_telemetry.c +80 -23
  128. package/deps/librdkafka/src/rdkafka_telemetry_encode.c +5 -1
  129. package/deps/librdkafka/src/rdkafka_telemetry_encode.h +20 -20
  130. package/deps/librdkafka/src/rdkafka_topic.c +220 -137
  131. package/deps/librdkafka/src/rdkafka_topic.h +14 -8
  132. package/deps/librdkafka/src/rdkafka_transport.c +59 -47
  133. package/deps/librdkafka/src/rdkafka_transport.h +5 -0
  134. package/deps/librdkafka/src/rdkafka_txnmgr.c +6 -1
  135. package/deps/librdkafka/src/rdmap.c +6 -1
  136. package/deps/librdkafka/src/rdrand.c +11 -1
  137. package/deps/librdkafka/src/rdtime.h +48 -17
  138. package/deps/librdkafka/src/rdunittest.c +42 -25
  139. package/deps/librdkafka/src/rdunittest.h +2 -0
  140. package/deps/librdkafka/src/rdwin32.h +1 -1
  141. package/deps/librdkafka/src/regexp.c +47 -42
  142. package/deps/librdkafka/src/tinycthread_extra.c +45 -21
  143. package/deps/librdkafka/src/tinycthread_extra.h +7 -3
  144. package/deps/librdkafka/src-cpp/rdkafkacpp.h +28 -2
  145. package/deps/librdkafka/tests/0001-multiobj.c +4 -0
  146. package/deps/librdkafka/tests/0004-conf.c +103 -36
  147. package/deps/librdkafka/tests/0009-mock_cluster.c +1 -0
  148. package/deps/librdkafka/tests/0011-produce_batch.c +2 -1
  149. package/deps/librdkafka/tests/0016-client_swname.c +18 -3
  150. package/deps/librdkafka/tests/0017-compression.c +6 -8
  151. package/deps/librdkafka/tests/0018-cgrp_term.c +8 -3
  152. package/deps/librdkafka/tests/0021-rkt_destroy.c +1 -0
  153. package/deps/librdkafka/tests/0022-consume_batch.c +4 -1
  154. package/deps/librdkafka/tests/0026-consume_pause.c +12 -14
  155. package/deps/librdkafka/tests/0028-long_topicnames.c +1 -1
  156. package/deps/librdkafka/tests/0029-assign_offset.c +6 -8
  157. package/deps/librdkafka/tests/0030-offset_commit.c +2 -2
  158. package/deps/librdkafka/tests/0033-regex_subscribe.c +33 -17
  159. package/deps/librdkafka/tests/0034-offset_reset.c +25 -4
  160. package/deps/librdkafka/tests/0036-partial_fetch.c +1 -0
  161. package/deps/librdkafka/tests/0038-performance.c +1 -0
  162. package/deps/librdkafka/tests/0040-io_event.c +11 -5
  163. package/deps/librdkafka/tests/0041-fetch_max_bytes.c +1 -0
  164. package/deps/librdkafka/tests/0044-partition_cnt.c +3 -2
  165. package/deps/librdkafka/tests/0045-subscribe_update.c +284 -20
  166. package/deps/librdkafka/tests/0047-partial_buf_tmout.c +1 -0
  167. package/deps/librdkafka/tests/0048-partitioner.c +1 -1
  168. package/deps/librdkafka/tests/0050-subscribe_adds.c +1 -0
  169. package/deps/librdkafka/tests/0051-assign_adds.c +1 -0
  170. package/deps/librdkafka/tests/0052-msg_timestamps.c +22 -4
  171. package/deps/librdkafka/tests/0053-stats_cb.cpp +12 -20
  172. package/deps/librdkafka/tests/0055-producer_latency.c +176 -3
  173. package/deps/librdkafka/tests/0056-balanced_group_mt.c +6 -2
  174. package/deps/librdkafka/tests/0058-log.cpp +3 -3
  175. package/deps/librdkafka/tests/0059-bsearch.cpp +8 -4
  176. package/deps/librdkafka/tests/0061-consumer_lag.cpp +25 -5
  177. package/deps/librdkafka/tests/0067-empty_topic.cpp +3 -0
  178. package/deps/librdkafka/tests/0068-produce_timeout.c +21 -23
  179. package/deps/librdkafka/tests/0069-consumer_add_parts.c +3 -7
  180. package/deps/librdkafka/tests/0075-retry.c +38 -1
  181. package/deps/librdkafka/tests/0076-produce_retry.c +2 -0
  182. package/deps/librdkafka/tests/0077-compaction.c +8 -2
  183. package/deps/librdkafka/tests/0080-admin_ut.c +34 -4
  184. package/deps/librdkafka/tests/0081-admin.c +455 -155
  185. package/deps/librdkafka/tests/0082-fetch_max_bytes.cpp +4 -0
  186. package/deps/librdkafka/tests/0083-cb_event.c +8 -3
  187. package/deps/librdkafka/tests/0084-destroy_flags.c +14 -18
  188. package/deps/librdkafka/tests/0085-headers.cpp +4 -0
  189. package/deps/librdkafka/tests/0086-purge.c +40 -7
  190. package/deps/librdkafka/tests/0088-produce_metadata_timeout.c +1 -1
  191. package/deps/librdkafka/tests/0089-max_poll_interval.c +12 -7
  192. package/deps/librdkafka/tests/0090-idempotence.c +1 -2
  193. package/deps/librdkafka/tests/0091-max_poll_interval_timeout.c +5 -7
  194. package/deps/librdkafka/tests/0092-mixed_msgver.c +6 -0
  195. package/deps/librdkafka/tests/0093-holb.c +8 -5
  196. package/deps/librdkafka/tests/0094-idempotence_msg_timeout.c +1 -0
  197. package/deps/librdkafka/tests/0097-ssl_verify.cpp +262 -70
  198. package/deps/librdkafka/tests/0098-consumer-txn.cpp +16 -16
  199. package/deps/librdkafka/tests/0099-commit_metadata.c +1 -1
  200. package/deps/librdkafka/tests/0101-fetch-from-follower.cpp +1 -1
  201. package/deps/librdkafka/tests/0102-static_group_rebalance.c +317 -16
  202. package/deps/librdkafka/tests/0103-transactions.c +7 -7
  203. package/deps/librdkafka/tests/0104-fetch_from_follower_mock.c +1 -0
  204. package/deps/librdkafka/tests/0105-transactions_mock.c +18 -11
  205. package/deps/librdkafka/tests/0106-cgrp_sess_timeout.c +35 -14
  206. package/deps/librdkafka/tests/0107-topic_recreate.c +2 -2
  207. package/deps/librdkafka/tests/0109-auto_create_topics.cpp +86 -26
  208. package/deps/librdkafka/tests/0110-batch_size.cpp +1 -2
  209. package/deps/librdkafka/tests/0112-assign_unknown_part.c +3 -14
  210. package/deps/librdkafka/tests/0113-cooperative_rebalance.cpp +248 -104
  211. package/deps/librdkafka/tests/0114-sticky_partitioning.cpp +3 -3
  212. package/deps/librdkafka/tests/0115-producer_auth.cpp +4 -1
  213. package/deps/librdkafka/tests/0116-kafkaconsumer_close.cpp +7 -5
  214. package/deps/librdkafka/tests/0117-mock_errors.c +15 -5
  215. package/deps/librdkafka/tests/0118-commit_rebalance.c +41 -8
  216. package/deps/librdkafka/tests/0119-consumer_auth.cpp +20 -1
  217. package/deps/librdkafka/tests/0120-asymmetric_subscription.c +8 -3
  218. package/deps/librdkafka/tests/0125-immediate_flush.c +1 -1
  219. package/deps/librdkafka/tests/0126-oauthbearer_oidc.c +323 -8
  220. package/deps/librdkafka/tests/0129-fetch_aborted_msgs.c +1 -0
  221. package/deps/librdkafka/tests/0132-strategy_ordering.c +12 -4
  222. package/deps/librdkafka/tests/0133-ssl_keys.c +28 -6
  223. package/deps/librdkafka/tests/0137-barrier_batch_consume.c +24 -14
  224. package/deps/librdkafka/tests/0139-offset_validation_mock.c +510 -2
  225. package/deps/librdkafka/tests/0140-commit_metadata.cpp +1 -1
  226. package/deps/librdkafka/tests/0142-reauthentication.c +37 -17
  227. package/deps/librdkafka/tests/0143-exponential_backoff_mock.c +39 -40
  228. package/deps/librdkafka/tests/0146-metadata_mock.c +76 -15
  229. package/deps/librdkafka/tests/0147-consumer_group_consumer_mock.c +952 -0
  230. package/deps/librdkafka/tests/0148-offset_fetch_commit_error_mock.c +563 -0
  231. package/deps/librdkafka/tests/0149-broker-same-host-port.c +140 -0
  232. package/deps/librdkafka/tests/0150-telemetry_mock.c +9 -6
  233. package/deps/librdkafka/tests/0151-purge-brokers.c +566 -0
  234. package/deps/librdkafka/tests/0152-rebootstrap.c +59 -0
  235. package/deps/librdkafka/tests/0153-memberid.c +128 -0
  236. package/deps/librdkafka/tests/CMakeLists.txt +6 -0
  237. package/deps/librdkafka/tests/LibrdkafkaTestApp.py +34 -9
  238. package/deps/librdkafka/tests/autotest.sh +0 -0
  239. package/deps/librdkafka/tests/broker_version_tests.py +0 -0
  240. package/deps/librdkafka/tests/buildbox.sh +0 -0
  241. package/deps/librdkafka/tests/cleanup-checker-tests.sh +0 -0
  242. package/deps/librdkafka/tests/cluster_testing.py +0 -0
  243. package/deps/librdkafka/tests/delete-test-topics.sh +0 -0
  244. package/deps/librdkafka/tests/fixtures/oauthbearer/jwt_assertion_template.json +10 -0
  245. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.intermediate.p12 +0 -0
  246. package/deps/librdkafka/tests/fixtures/ssl/client.keystore.p12 +0 -0
  247. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.intermediate.pem +72 -0
  248. package/deps/librdkafka/tests/fixtures/ssl/client2.certificate.pem +42 -101
  249. package/deps/librdkafka/tests/fixtures/ssl/client2.intermediate.key +46 -0
  250. package/deps/librdkafka/tests/fixtures/ssl/client2.key +41 -29
  251. package/deps/librdkafka/tests/fixtures/ssl/create_keys.sh +112 -37
  252. package/deps/librdkafka/tests/gen-ssl-certs.sh +0 -0
  253. package/deps/librdkafka/tests/interactive_broker_version.py +0 -0
  254. package/deps/librdkafka/tests/java/Makefile +2 -1
  255. package/deps/librdkafka/tests/java/run-class.sh +0 -0
  256. package/deps/librdkafka/tests/lz4_manual_test.sh +0 -0
  257. package/deps/librdkafka/tests/multi-broker-version-test.sh +0 -0
  258. package/deps/librdkafka/tests/parse-refcnt.sh +0 -0
  259. package/deps/librdkafka/tests/performance_plot.py +0 -0
  260. package/deps/librdkafka/tests/requirements.txt +1 -1
  261. package/deps/librdkafka/tests/run-all-tests.sh +79 -0
  262. package/deps/librdkafka/tests/run-consumer-tests.sh +0 -0
  263. package/deps/librdkafka/tests/run-producer-tests.sh +0 -0
  264. package/deps/librdkafka/tests/run-test-batches.py +157 -0
  265. package/deps/librdkafka/tests/run-test.sh +0 -0
  266. package/deps/librdkafka/tests/sasl_test.py +0 -0
  267. package/deps/librdkafka/tests/test.c +478 -18
  268. package/deps/librdkafka/tests/test.h +62 -13
  269. package/deps/librdkafka/tests/testcpp.cpp +6 -1
  270. package/deps/librdkafka/tests/testcpp.h +30 -2
  271. package/deps/librdkafka/tests/testshared.h +16 -2
  272. package/deps/librdkafka/tests/tools/stats/graph.py +0 -0
  273. package/deps/librdkafka/tests/tools/stats/to_csv.py +1 -1
  274. package/deps/librdkafka/tests/trivup/trivup-0.14.0.tar.gz +0 -0
  275. package/deps/librdkafka/tests/until-fail.sh +0 -0
  276. package/deps/librdkafka/tests/xxxx-metadata.cpp +1 -1
  277. package/deps/librdkafka/vcpkg.json +1 -1
  278. package/deps/librdkafka/win32/setup-msys2.ps1 +24 -8
  279. package/deps/librdkafka/win32/setup-vcpkg.ps1 +28 -6
  280. package/deps/librdkafka/win32/tests/tests.vcxproj +6 -0
  281. package/deps/librdkafka/win32/wingetopt.h +5 -4
  282. package/errors.d.ts +27 -1
  283. package/lib/error.js +29 -3
  284. package/make_docs.sh +0 -0
  285. package/package.json +11 -11
  286. package/run_docker.sh +0 -0
  287. package/src/callbacks.cc +6 -9
  288. package/src/callbacks.h +1 -5
  289. package/test/kafka-consumer-worker.js +10 -7
  290. package/test/kafka-consumer.spec.js +32 -21
  291. package/ci/update-version.js +0 -123
  292. package/deps/librdkafka/.semaphore/project.yml +0 -43
  293. package/deps/librdkafka/.semaphore/project_public.yml +0 -20
  294. package/deps/librdkafka/win32/install-openssl.ps1 +0 -33
@@ -40,6 +40,10 @@
40
40
  #include <curl/curl.h>
41
41
  #include "rdhttp.h"
42
42
 
43
+ #if WITH_SSL
44
+ #include "rdkafka_ssl.h"
45
+ #endif
46
+
43
47
  /** Maximum response size, increase as necessary. */
44
48
  #define RD_HTTP_RESPONSE_SIZE_MAX 1024 * 1024 * 500 /* 500kb */
45
49
 
@@ -128,8 +132,171 @@ rd_http_req_write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
128
132
  return nmemb;
129
133
  }
130
134
 
131
- rd_http_error_t *rd_http_req_init(rd_http_req_t *hreq, const char *url) {
135
+ #if WITH_SSL
136
+ /**
137
+ * @brief Callback function for setting up the SSL_CTX for HTTPS requests.
138
+ *
139
+ * This function sets the default CA paths for the SSL_CTX, and if that fails,
140
+ * it attempts to probe and set a default CA location. If `probe` is forced
141
+ * it skips the default CA paths and directly probes for CA certificates.
142
+ *
143
+ * On Windows, it attempts to load CA root certificates from the
144
+ * configured Windows certificate stores before falling back to the default.
145
+ *
146
+ * @return `CURLE_OK` on success, or `CURLE_SSL_CACERT_BADFILE` on failure.
147
+ */
148
+ static CURLcode
149
+ rd_http_ssl_ctx_function(CURL *curl, void *sslctx, void *userptr) {
150
+ SSL_CTX *ctx = (SSL_CTX *)sslctx;
151
+ rd_kafka_t *rk = (rd_kafka_t *)userptr;
152
+ int r = -1;
153
+ rd_bool_t force_probe =
154
+ !rd_strcmp(rk->rk_conf.https.ca_location, "probe");
155
+ rd_bool_t use_probe = force_probe;
156
+
157
+ #if WITH_STATIC_LIB_libcrypto
158
+ /* We fallback to `probe` when statically linked. */
159
+ use_probe = rd_true;
160
+ #endif
161
+
162
+ #ifdef _WIN32
163
+ /* Attempt to load CA root certificates from the
164
+ * configured Windows certificate stores. */
165
+ r = rd_kafka_ssl_win_load_cert_stores(rk, "https", ctx,
166
+ rk->rk_conf.ssl.ca_cert_stores);
167
+ if (r == 0) {
168
+ rd_kafka_log(rk, LOG_NOTICE, "CERTSTORE",
169
+ "No CA certificates loaded for `https` from "
170
+ "Windows certificate stores: "
171
+ "falling back to default OpenSSL CA paths");
172
+ r = -1;
173
+ } else if (r == -1)
174
+ rd_kafka_log(rk, LOG_NOTICE, "CERTSTORE",
175
+ "Failed to load CA certificates for `https` from "
176
+ "Windows certificate stores: "
177
+ "falling back to default OpenSSL CA paths");
178
+
179
+ if (r != -1) {
180
+ rd_kafka_dbg(rk, SECURITY, "SSL",
181
+ "Successfully loaded CA certificates for `https` "
182
+ "from Windows certificate stores");
183
+ return CURLE_OK; /* Success, CA certs loaded on Windows */
184
+ }
185
+ #endif
186
+
187
+ if (!force_probe) {
188
+ /* Previous default behavior: use predefined paths set when
189
+ * building OpenSSL. */
190
+ char errstr[512];
191
+ r = SSL_CTX_set_default_verify_paths(ctx);
192
+ if (r == 1) {
193
+ rd_kafka_dbg(rk, SECURITY, "SSL",
194
+ "SSL_CTX_set_default_verify_paths() "
195
+ "for `https` "
196
+ "succeeded");
197
+ return CURLE_OK; /* Success */
198
+ }
199
+
200
+ /* Read error and clear the error stack. */
201
+ rd_kafka_ssl_error0(rk, NULL, "https", errstr, sizeof(errstr));
202
+ rd_kafka_dbg(rk, SECURITY, "SSL",
203
+ "SSL_CTX_set_default_verify_paths() "
204
+ "for `https` "
205
+ "failed: %s",
206
+ errstr);
207
+ }
208
+
209
+ if (use_probe) {
210
+ /* We asked for probing or we're using
211
+ * a statically linked version of OpenSSL. */
212
+
213
+ r = rd_kafka_ssl_probe_and_set_default_ca_location(rk, "https",
214
+ ctx);
215
+ if (r == 0)
216
+ return CURLE_OK;
217
+ }
218
+
219
+ return CURLE_SSL_CACERT_BADFILE;
220
+ }
221
+
222
+ static void rd_http_ssl_configure(rd_kafka_t *rk, CURL *hreq_curl) {
223
+ rd_bool_t force_probe =
224
+ !rd_strcmp(rk->rk_conf.https.ca_location, "probe");
225
+
226
+ if (!force_probe && rk->rk_conf.https.ca_location) {
227
+ rd_bool_t is_dir;
228
+ rd_kafka_dbg(rk, SECURITY, "SSL",
229
+ "Setting `https` CA certs from "
230
+ "configured location: %s",
231
+ rk->rk_conf.https.ca_location);
232
+ if (rd_file_stat(rk->rk_conf.https.ca_location, &is_dir)) {
233
+ if (is_dir) {
234
+ curl_easy_setopt(hreq_curl, CURLOPT_CAPATH,
235
+ rk->rk_conf.https.ca_location);
236
+ curl_easy_setopt(hreq_curl, CURLOPT_CAINFO,
237
+ NULL);
238
+ } else {
239
+ curl_easy_setopt(hreq_curl, CURLOPT_CAPATH,
240
+ NULL);
241
+ curl_easy_setopt(hreq_curl, CURLOPT_CAINFO,
242
+ rk->rk_conf.https.ca_location);
243
+ }
244
+ } else {
245
+ /* Path doesn't exist, don't set any trusted
246
+ * certificate. */
247
+ curl_easy_setopt(hreq_curl, CURLOPT_CAINFO, NULL);
248
+ curl_easy_setopt(hreq_curl, CURLOPT_CAPATH, NULL);
249
+ }
250
+ } else if (!force_probe && rk->rk_conf.https.ca_pem) {
251
+ #if CURL_AT_LEAST_VERSION(7, 77, 0)
252
+ struct curl_blob ca_blob = {
253
+ .data = rk->rk_conf.https.ca_pem,
254
+ .len = strlen(rk->rk_conf.https.ca_pem),
255
+ .flags = CURL_BLOB_COPY};
256
+ rd_kafka_dbg(rk, SECURITY, "SSL",
257
+ "Setting `https` CA certs from "
258
+ "configured PEM string");
259
+ curl_easy_setopt(hreq_curl, CURLOPT_CAINFO_BLOB, &ca_blob);
260
+ #endif
261
+ /* Only the blob should be set, no default paths. */
262
+ curl_easy_setopt(hreq_curl, CURLOPT_CAINFO, NULL);
263
+ curl_easy_setopt(hreq_curl, CURLOPT_CAPATH, NULL);
264
+ } else {
265
+ curl_easy_setopt(hreq_curl, CURLOPT_SSL_CTX_FUNCTION,
266
+ rd_http_ssl_ctx_function);
267
+ curl_easy_setopt(hreq_curl, CURLOPT_SSL_CTX_DATA, rk);
268
+ }
269
+ }
270
+ #endif
271
+
272
+ /**
273
+ * @brief Check if the error returned from HTTP(S) is temporary or not.
274
+ *
275
+ * @returns If the \p error_code is temporary, return rd_true,
276
+ * otherwise return rd_false.
277
+ *
278
+ * @locality Any thread.
279
+ * @locks None.
280
+ * @locks_acquired None.
281
+ */
282
+ static rd_bool_t rd_http_is_failure_temporary(int error_code) {
283
+ switch (error_code) {
284
+ case 408: /**< Request timeout */
285
+ case 425: /**< Too early */
286
+ case 429: /**< Too many requests */
287
+ case 500: /**< Internal server error */
288
+ case 502: /**< Bad gateway */
289
+ case 503: /**< Service unavailable */
290
+ case 504: /**< Gateway timeout */
291
+ return rd_true;
292
+
293
+ default:
294
+ return rd_false;
295
+ }
296
+ }
132
297
 
298
+ rd_http_error_t *
299
+ rd_http_req_init(rd_kafka_t *rk, rd_http_req_t *hreq, const char *url) {
133
300
  memset(hreq, 0, sizeof(*hreq));
134
301
 
135
302
  hreq->hreq_curl = curl_easy_init();
@@ -139,8 +306,15 @@ rd_http_error_t *rd_http_req_init(rd_http_req_t *hreq, const char *url) {
139
306
  hreq->hreq_buf = rd_buf_new(1, 1024);
140
307
 
141
308
  curl_easy_setopt(hreq->hreq_curl, CURLOPT_URL, url);
309
+ #if CURL_AT_LEAST_VERSION(7, 85, 0)
310
+ curl_easy_setopt(hreq->hreq_curl, CURLOPT_PROTOCOLS_STR, "http,https");
311
+ #else
312
+ /* As of 06/10/2025 Debian 10 and CentOS Stream 9 ship with
313
+ * older CURL versions, remove this condition once they're not supported
314
+ * anymore. */
142
315
  curl_easy_setopt(hreq->hreq_curl, CURLOPT_PROTOCOLS,
143
316
  CURLPROTO_HTTP | CURLPROTO_HTTPS);
317
+ #endif
144
318
  curl_easy_setopt(hreq->hreq_curl, CURLOPT_MAXREDIRS, 16);
145
319
  curl_easy_setopt(hreq->hreq_curl, CURLOPT_TIMEOUT, 30);
146
320
  curl_easy_setopt(hreq->hreq_curl, CURLOPT_ERRORBUFFER,
@@ -150,6 +324,10 @@ rd_http_error_t *rd_http_req_init(rd_http_req_t *hreq, const char *url) {
150
324
  rd_http_req_write_cb);
151
325
  curl_easy_setopt(hreq->hreq_curl, CURLOPT_WRITEDATA, (void *)hreq);
152
326
 
327
+ #if WITH_SSL
328
+ rd_http_ssl_configure(rk, hreq->hreq_curl);
329
+ #endif
330
+
153
331
  return NULL;
154
332
  }
155
333
 
@@ -191,35 +369,113 @@ const char *rd_http_req_get_content_type(rd_http_req_t *hreq) {
191
369
 
192
370
  /**
193
371
  * @brief Perform a blocking HTTP(S) request to \p url.
372
+ * Retries the request \p retries times with linear backoff.
373
+ * Interval of \p retry_ms milliseconds is used between retries.
194
374
  *
195
- * Returns the response (even if there's a HTTP error code returned)
196
- * in \p *rbufp.
375
+ * @param url The URL to perform the request to.
376
+ * @param headers_array Array of HTTP(S) headers to set, each element
377
+ * is a string in the form "key: value"
378
+ * @param headers_array_cnt Number of elements in \p headers_array.
379
+ * @param timeout_s Timeout in seconds for the request, 0 means default
380
+ * `rd_http_req_init()` timeout.
381
+ * @param retries Number of retries to perform on failure.
382
+ * @param retry_ms Milliseconds to wait between retries.
383
+ * @param rbufp (out) Pointer to a buffer that will be filled with the response.
384
+ * @param content_type (out, optional) Pointer to a string that will be filled
385
+ * with the content type of the response, if not NULL.
386
+ * @param response_code (out, optional) Pointer to an integer that will be
387
+ * filled with the HTTP response code, if not NULL.
197
388
  *
198
- * Returns NULL on success (HTTP response code < 400), or an error
199
- * object on transport or HTTP error - this error object must be destroyed
200
- * by calling rd_http_error_destroy(). In case of HTTP error the \p *rbufp
201
- * may be filled with the error response.
389
+ * @return Returns NULL on success (HTTP response code < 400), or an error
390
+ * object on transport or HTTP error.
391
+ *
392
+ * @remark Returned error object, when non-NULL, must be destroyed
393
+ * by calling rd_http_error_destroy().
394
+ *
395
+ * @locality Any thread.
396
+ * @locks None.
397
+ * @locks_acquired None.
202
398
  */
203
- rd_http_error_t *rd_http_get(const char *url, rd_buf_t **rbufp) {
399
+ rd_http_error_t *rd_http_get(rd_kafka_t *rk,
400
+ const char *url,
401
+ char **headers_array,
402
+ size_t headers_array_cnt,
403
+ int timeout_s,
404
+ int retries,
405
+ int retry_ms,
406
+ rd_buf_t **rbufp,
407
+ char **content_type,
408
+ int *response_code) {
204
409
  rd_http_req_t hreq;
205
- rd_http_error_t *herr;
410
+ rd_http_error_t *herr = NULL;
411
+ struct curl_slist *headers = NULL;
412
+ char *header;
413
+ int i;
414
+ size_t len, j;
206
415
 
207
416
  *rbufp = NULL;
417
+ if (content_type)
418
+ *content_type = NULL;
419
+ if (response_code)
420
+ *response_code = -1;
208
421
 
209
- herr = rd_http_req_init(&hreq, url);
422
+ herr = rd_http_req_init(rk, &hreq, url);
210
423
  if (unlikely(herr != NULL))
211
424
  return herr;
212
425
 
213
- herr = rd_http_req_perform_sync(&hreq);
214
- if (herr) {
215
- rd_http_req_destroy(&hreq);
216
- return herr;
426
+ for (j = 0; j < headers_array_cnt; j++) {
427
+ header = headers_array[j];
428
+ if (header && *header)
429
+ headers = curl_slist_append(headers, header);
430
+ }
431
+ curl_easy_setopt(hreq.hreq_curl, CURLOPT_HTTPHEADER, headers);
432
+ if (timeout_s > 0)
433
+ curl_easy_setopt(hreq.hreq_curl, CURLOPT_TIMEOUT, timeout_s);
434
+
435
+ for (i = 0; i <= retries; i++) {
436
+ if (rd_kafka_terminating(rk)) {
437
+ herr = rd_http_error_new(-1, "Terminating");
438
+ goto done;
439
+ }
440
+
441
+ herr = rd_http_req_perform_sync(&hreq);
442
+ len = rd_buf_len(hreq.hreq_buf);
443
+
444
+ if (!herr) {
445
+ if (len > 0)
446
+ break; /* Success */
447
+ /* Empty response */
448
+ goto done;
449
+ }
450
+
451
+ /* Retry if HTTP(S) request returns temporary error and there
452
+ * are remaining retries, else fail. */
453
+ if (i == retries || !rd_http_is_failure_temporary(herr->code)) {
454
+ goto done;
455
+ }
456
+
457
+ /* Retry */
458
+ rd_http_error_destroy(herr);
459
+ rd_usleep(retry_ms * 1000 * (i + 1), &rk->rk_terminate);
217
460
  }
218
461
 
219
462
  *rbufp = hreq.hreq_buf;
220
463
  hreq.hreq_buf = NULL;
221
464
 
222
- return NULL;
465
+ if (content_type) {
466
+ const char *ct = rd_http_req_get_content_type(&hreq);
467
+ if (ct && *ct)
468
+ *content_type = rd_strdup(ct);
469
+ else
470
+ *content_type = NULL;
471
+ }
472
+ if (response_code)
473
+ *response_code = hreq.hreq_code;
474
+
475
+ done:
476
+ RD_IF_FREE(headers, curl_slist_free_all);
477
+ rd_http_req_destroy(&hreq);
478
+ return herr;
223
479
  }
224
480
 
225
481
 
@@ -257,31 +513,6 @@ rd_http_error_t *rd_http_parse_json(rd_http_req_t *hreq, cJSON **jsonp) {
257
513
  }
258
514
 
259
515
 
260
- /**
261
- * @brief Check if the error returned from HTTP(S) is temporary or not.
262
- *
263
- * @returns If the \p error_code is temporary, return rd_true,
264
- * otherwise return rd_false.
265
- *
266
- * @locality Any thread.
267
- */
268
- static rd_bool_t rd_http_is_failure_temporary(int error_code) {
269
- switch (error_code) {
270
- case 408: /**< Request timeout */
271
- case 425: /**< Too early */
272
- case 429: /**< Too many requests */
273
- case 500: /**< Internal server error */
274
- case 502: /**< Bad gateway */
275
- case 503: /**< Service unavailable */
276
- case 504: /**< Gateway timeout */
277
- return rd_true;
278
-
279
- default:
280
- return rd_false;
281
- }
282
- }
283
-
284
-
285
516
  /**
286
517
  * @brief Perform a blocking HTTP(S) request to \p url with
287
518
  * HTTP(S) headers and data with \p timeout_s.
@@ -310,7 +541,7 @@ rd_http_error_t *rd_http_post_expect_json(rd_kafka_t *rk,
310
541
  size_t len;
311
542
  const char *content_type;
312
543
 
313
- herr = rd_http_req_init(&hreq, url);
544
+ herr = rd_http_req_init(rk, &hreq, url);
314
545
  if (unlikely(herr != NULL))
315
546
  return herr;
316
547
 
@@ -368,6 +599,39 @@ rd_http_error_t *rd_http_post_expect_json(rd_kafka_t *rk,
368
599
  return herr;
369
600
  }
370
601
 
602
+ /**
603
+ * @brief Append \p params to \p url, taking care of existing query parameters
604
+ * and hash fragments. \p params must be already URL encoded.
605
+ *
606
+ * @returns A newly allocated string with the appended parameters or NULL
607
+ * on error.
608
+ */
609
+ char *rd_http_get_params_append(const char *url, const char *params) {
610
+ char *new_url;
611
+ CURLU *u = curl_url();
612
+ CURLUcode rc = curl_url_set(u, CURLUPART_URL, url, 0);
613
+ if (rc != CURLUE_OK)
614
+ goto err;
615
+
616
+ rc = curl_url_set(u, CURLUPART_QUERY, params, CURLU_APPENDQUERY);
617
+ if (rc != CURLUE_OK)
618
+ goto err;
619
+
620
+ rc = curl_url_set(u, CURLUPART_FRAGMENT, NULL,
621
+ 0); // remove hash fragment
622
+ if (rc != CURLUE_OK)
623
+ goto err;
624
+
625
+ rc = curl_url_get(u, CURLUPART_URL, &new_url, 0);
626
+ if (rc != CURLUE_OK)
627
+ goto err;
628
+
629
+ curl_url_cleanup(u);
630
+ return new_url;
631
+ err:
632
+ curl_url_cleanup(u);
633
+ return NULL;
634
+ }
371
635
 
372
636
  /**
373
637
  * @brief Same as rd_http_get() but requires a JSON response.
@@ -375,66 +639,51 @@ rd_http_error_t *rd_http_post_expect_json(rd_kafka_t *rk,
375
639
  *
376
640
  * Same error semantics as rd_http_get().
377
641
  */
378
- rd_http_error_t *rd_http_get_json(const char *url, cJSON **jsonp) {
379
- rd_http_req_t hreq;
642
+ rd_http_error_t *rd_http_get_json(rd_kafka_t *rk,
643
+ const char *url,
644
+ char **headers_array,
645
+ size_t headers_array_cnt,
646
+ int timeout_s,
647
+ int retries,
648
+ int retry_ms,
649
+ cJSON **jsonp) {
380
650
  rd_http_error_t *herr;
381
- rd_slice_t slice;
382
- size_t len;
383
- const char *content_type;
384
- char *raw_json;
385
- const char *end;
651
+ int response_code;
652
+ char *content_type;
653
+ rd_buf_t *rbuf;
654
+ char **headers_array_new =
655
+ rd_calloc(headers_array_cnt + 1, sizeof(*headers_array_new));
656
+ rd_http_req_t hreq;
386
657
 
387
658
  *jsonp = NULL;
388
659
 
389
- herr = rd_http_req_init(&hreq, url);
390
- if (unlikely(herr != NULL))
391
- return herr;
660
+ memcpy(headers_array_new, headers_array,
661
+ headers_array_cnt * sizeof(*headers_array_new));
662
+ headers_array_new[headers_array_cnt++] = "Accept: application/json";
392
663
 
393
- // FIXME: send Accept: json.. header?
664
+ herr = rd_http_get(rk, url, headers_array_new, headers_array_cnt,
665
+ timeout_s, retries, retry_ms, &rbuf, &content_type,
666
+ &response_code);
667
+ rd_free(headers_array_new);
394
668
 
395
- herr = rd_http_req_perform_sync(&hreq);
396
- len = rd_buf_len(hreq.hreq_buf);
397
- if (herr && len == 0) {
398
- rd_http_req_destroy(&hreq);
669
+ if (unlikely(herr != NULL))
399
670
  return herr;
400
- }
401
-
402
- if (len == 0) {
403
- /* Empty response: create empty JSON object */
404
- *jsonp = cJSON_CreateObject();
405
- rd_http_req_destroy(&hreq);
406
- return NULL;
407
- }
408
-
409
- content_type = rd_http_req_get_content_type(&hreq);
410
671
 
411
672
  if (!content_type || rd_strncasecmp(content_type, "application/json",
412
673
  strlen("application/json"))) {
413
- if (!herr)
414
- herr = rd_http_error_new(
415
- hreq.hreq_code, "Response is not JSON encoded: %s",
416
- content_type ? content_type : "(n/a)");
417
- rd_http_req_destroy(&hreq);
674
+ herr = rd_http_error_new(response_code,
675
+ "Response is not JSON encoded: %s",
676
+ content_type ? content_type : "(n/a)");
677
+ RD_IF_FREE(rbuf, rd_buf_destroy_free);
678
+ RD_IF_FREE(content_type, rd_free);
418
679
  return herr;
419
680
  }
420
681
 
421
- /* cJSON requires the entire input to parse in contiguous memory. */
422
- rd_slice_init_full(&slice, hreq.hreq_buf);
423
- raw_json = rd_malloc(len + 1);
424
- rd_slice_read(&slice, raw_json, len);
425
- raw_json[len] = '\0';
426
-
427
- /* Parse JSON */
428
- end = NULL;
429
- *jsonp = cJSON_ParseWithOpts(raw_json, &end, 0);
430
- if (!*jsonp && !herr)
431
- herr = rd_http_error_new(hreq.hreq_code,
432
- "Failed to parse JSON response "
433
- "at %" PRIusz "/%" PRIusz,
434
- (size_t)(end - raw_json), len);
435
-
436
- rd_free(raw_json);
437
- rd_http_req_destroy(&hreq);
682
+ hreq.hreq_buf = rbuf;
683
+ hreq.hreq_code = response_code;
684
+ herr = rd_http_parse_json(&hreq, jsonp);
685
+ RD_IF_FREE(rbuf, rd_buf_destroy_free);
686
+ RD_IF_FREE(content_type, rd_free);
438
687
 
439
688
  return herr;
440
689
  }
@@ -445,35 +694,28 @@ void rd_http_global_init(void) {
445
694
  }
446
695
 
447
696
 
448
- /**
449
- * @brief Unittest. Requires a (local) webserver to be set with env var
450
- * RD_UT_HTTP_URL=http://localhost:1234/some-path
451
- *
452
- * This server must return a JSON object or array containing at least one
453
- * object on the main URL with a 2xx response code,
454
- * and 4xx response on $RD_UT_HTTP_URL/error (with whatever type of body).
455
- */
456
-
457
- int unittest_http(void) {
697
+ int unittest_http_get(void) {
458
698
  const char *base_url = rd_getenv("RD_UT_HTTP_URL", NULL);
459
699
  char *error_url;
460
700
  size_t error_url_size;
461
701
  cJSON *json, *jval;
462
702
  rd_http_error_t *herr;
463
703
  rd_bool_t empty;
704
+ rd_kafka_t *rk;
464
705
 
465
706
  if (!base_url || !*base_url)
466
707
  RD_UT_SKIP("RD_UT_HTTP_URL environment variable not set");
467
708
 
468
709
  RD_UT_BEGIN();
469
710
 
711
+ rk = rd_calloc(1, sizeof(*rk));
470
712
  error_url_size = strlen(base_url) + strlen("/error") + 1;
471
713
  error_url = rd_alloca(error_url_size);
472
714
  rd_snprintf(error_url, error_url_size, "%s/error", base_url);
473
715
 
474
716
  /* Try the base url first, parse its JSON and extract a key-value. */
475
717
  json = NULL;
476
- herr = rd_http_get_json(base_url, &json);
718
+ herr = rd_http_get_json(rk, base_url, NULL, 0, 5, 1, 1000, &json);
477
719
  RD_UT_ASSERT(!herr, "Expected get_json(%s) to succeed, got: %s",
478
720
  base_url, herr->errstr);
479
721
 
@@ -493,7 +735,7 @@ int unittest_http(void) {
493
735
 
494
736
  /* Try the error URL, verify error code. */
495
737
  json = NULL;
496
- herr = rd_http_get_json(error_url, &json);
738
+ herr = rd_http_get_json(rk, error_url, NULL, 0, 5, 1, 1000, &json);
497
739
  RD_UT_ASSERT(herr != NULL, "Expected get_json(%s) to fail", error_url);
498
740
  RD_UT_ASSERT(herr->code >= 400,
499
741
  "Expected get_json(%s) error code >= "
@@ -507,6 +749,82 @@ int unittest_http(void) {
507
749
  if (json)
508
750
  cJSON_Delete(json);
509
751
  rd_http_error_destroy(herr);
752
+ rd_free(rk);
510
753
 
511
754
  RD_UT_PASS();
512
755
  }
756
+
757
+ int unittest_http_get_params_append(void) {
758
+ rd_kafka_t *rk;
759
+ char *res;
760
+ RD_UT_BEGIN();
761
+ char *tests[] = {"http://localhost:1234",
762
+ "",
763
+ "http://localhost:1234/",
764
+
765
+ "http://localhost:1234/",
766
+ "a=1",
767
+ "http://localhost:1234/?a=1",
768
+
769
+ "https://localhost:1234/",
770
+ "a=1&b=2",
771
+ "https://localhost:1234/?a=1&b=2",
772
+
773
+ "http://mydomain.com/?a=1",
774
+ "c=hi",
775
+ "http://mydomain.com/?a=1&c=hi",
776
+
777
+ "https://mydomain.com/?",
778
+ "c=hi",
779
+ "https://mydomain.com/?c=hi",
780
+
781
+ "http://localhost:1234/path?a=1&b=2#&c=3",
782
+ "c=hi",
783
+ "http://localhost:1234/path?a=1&b=2&c=hi",
784
+
785
+ "http://localhost:1234#?c=3",
786
+ "a=1",
787
+ "http://localhost:1234/?a=1",
788
+
789
+ "https://otherdomain.io/path?a=1&#c=3",
790
+ "b=2",
791
+ "https://otherdomain.io/path?a=1&b=2",
792
+ NULL};
793
+
794
+
795
+ res = rd_http_get_params_append("", "");
796
+ RD_UT_ASSERT(!res, "Expected NULL result, got: \"%s\"", res);
797
+ res = rd_http_get_params_append("", "a=2&b=3");
798
+ RD_UT_ASSERT(!res, "Expected NULL result, got: \"%s\"", res);
799
+
800
+ char **test = tests;
801
+ rk = rd_calloc(1, sizeof(*rk));
802
+ while (test[0]) {
803
+ res = rd_http_get_params_append(test[0], test[1]);
804
+ RD_UT_ASSERT(!strcmp(res, test[2]),
805
+ "Expected \"%s\", got: \"%s\"", test[2], res);
806
+ rd_free(res);
807
+ test += 3;
808
+ }
809
+ rd_free(rk);
810
+
811
+ RD_UT_PASS();
812
+ }
813
+
814
+ /**
815
+ * @brief Unittest. Requires a (local) webserver to be set with env var
816
+ * RD_UT_HTTP_URL=http://localhost:1234/some-path
817
+ *
818
+ * This server must return a JSON object or array containing at least one
819
+ * object on the main URL with a 2xx response code,
820
+ * and 4xx response on $RD_UT_HTTP_URL/error (with whatever type of body).
821
+ */
822
+
823
+ int unittest_http(void) {
824
+ int fails = 0;
825
+
826
+ fails += unittest_http_get();
827
+ fails += unittest_http_get_params_append();
828
+
829
+ return fails;
830
+ }