gpgme 2.0.25 → 2.0.26

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c7d5f64ee6914eb120fbd884d55f5f7d9decfadc4c4ca86deb663696190517a
4
- data.tar.gz: c58815644c21156c37333b27cc0de594a330f34aa040f3533f44aa8b9706b83b
3
+ metadata.gz: 6fe08b2c986a5e8b47b37c96aec3bca4a490ae41ec56d8b596746e3392632e11
4
+ data.tar.gz: 15191272b4aefcf3df3cfec3d2bccc51eb41863036e8e746bf68a22f6c22e323
5
5
  SHA512:
6
- metadata.gz: 0775b291a09719b998107266313e0aa2185ebc6b337e4f038464440ff742df705dd23097f7041ad674a0cdac87400c45427d0b3094c96491002192a2f179a4ca
7
- data.tar.gz: 335c6520b7362f7f0e382ac852564065abde3a82367d299133a87c97cde3940cfb65fd37c4ff36c470b1d2c4123128f281aab14a45efdabe719828435ccd5761
6
+ metadata.gz: fa950f73817ce3afce83d1f0d2fb5ef9c2d1dc7a251f12376c079ef6b685f8d7b83f5242df381b1cac3632e5aa4946f8eb38947ad3d3e1b64ee24b7ddc2c6a5a
7
+ data.tar.gz: a77cf22e0fc1c41936c537a3a47a4c651ec1f8b2d7e9a8a0708d99ffc011b887b339cc57ecf82114370f3a644ce27eb6e6e4483e5277db8af03dc05d89dc854e
data/ext/gpgme/extconf.rb CHANGED
@@ -225,6 +225,19 @@ End
225
225
  end
226
226
 
227
227
  have_func('gpgme_op_export_keys')
228
+ have_const('GPGME_ENCRYPT_ALWAYS_TRUST', 'gpgme.h')
229
+ have_const('GPGME_ENCRYPT_NO_ENCRYPT_TO', 'gpgme.h')
230
+ have_const('GPGME_ENCRYPT_PREPARE', 'gpgme.h')
231
+ have_const('GPGME_ENCRYPT_EXPECT_SIGN', 'gpgme.h')
232
+ have_const('GPGME_ENCRYPT_NO_COMPRESS', 'gpgme.h')
233
+ have_const('GPGME_ENCRYPT_SYMMETRIC', 'gpgme.h')
234
+ have_const('GPGME_ENCRYPT_THROW_KEYIDS', 'gpgme.h')
235
+ have_const('GPGME_ENCRYPT_WRAP', 'gpgme.h')
236
+ have_const('GPGME_ENCRYPT_WANT_ADDRESS', 'gpgme.h')
237
+ have_const('GPGME_ENCRYPT_ARCHIVE', 'gpgme.h')
238
+ have_const('GPGME_ENCRYPT_FILE', 'gpgme.h')
239
+ have_const('GPGME_ENCRYPT_ADD_RECP', 'gpgme.h')
240
+ have_const('GPGME_ENCRYPT_CHG_RECP', 'gpgme.h')
228
241
 
229
242
  create_makefile ('gpgme_n')
230
243
 
data/ext/gpgme/gpgme_n.c CHANGED
@@ -88,30 +88,105 @@
88
88
  #define RSTRING_LEN(a) RSTRING(a)->len
89
89
  #endif
90
90
 
91
+ /* TypedData type definitions for Ruby 3.x compatibility */
92
+ static void
93
+ gpgme_data_free(void *ptr)
94
+ {
95
+ if (ptr)
96
+ gpgme_data_release((gpgme_data_t)ptr);
97
+ }
98
+
99
+ static const rb_data_type_t gpgme_data_type = {
100
+ .wrap_struct_name = "GPGME::Data",
101
+ .function = {
102
+ .dmark = NULL,
103
+ .dfree = gpgme_data_free,
104
+ .dsize = NULL,
105
+ },
106
+ .data = NULL,
107
+ .flags = 0,
108
+ };
109
+
110
+ static void
111
+ gpgme_ctx_free(void *ptr)
112
+ {
113
+ if (ptr)
114
+ gpgme_release((gpgme_ctx_t)ptr);
115
+ }
116
+
117
+ static const rb_data_type_t gpgme_ctx_type = {
118
+ .wrap_struct_name = "GPGME::Ctx",
119
+ .function = {
120
+ .dmark = NULL,
121
+ .dfree = gpgme_ctx_free,
122
+ .dsize = NULL,
123
+ },
124
+ .data = NULL,
125
+ .flags = 0,
126
+ };
127
+
128
+ static void
129
+ gpgme_key_free(void *ptr)
130
+ {
131
+ if (ptr)
132
+ gpgme_key_unref((gpgme_key_t)ptr);
133
+ }
134
+
135
+ static const rb_data_type_t gpgme_key_type = {
136
+ .wrap_struct_name = "GPGME::Key",
137
+ .function = {
138
+ .dmark = NULL,
139
+ .dfree = gpgme_key_free,
140
+ .dsize = NULL,
141
+ },
142
+ .data = NULL,
143
+ .flags = 0,
144
+ };
145
+
146
+ #if defined(GPGME_VERSION_NUMBER) && GPGME_VERSION_NUMBER < 0x020000
147
+ static void
148
+ gpgme_trust_item_free(void *ptr)
149
+ {
150
+ if (ptr)
151
+ gpgme_trust_item_unref((gpgme_trust_item_t)ptr);
152
+ }
153
+
154
+ static const rb_data_type_t gpgme_trust_item_type = {
155
+ .wrap_struct_name = "GPGME::TrustItem",
156
+ .function = {
157
+ .dmark = NULL,
158
+ .dfree = gpgme_trust_item_free,
159
+ .dsize = NULL,
160
+ },
161
+ .data = NULL,
162
+ .flags = 0,
163
+ };
164
+ #endif
165
+
91
166
  #define WRAP_GPGME_DATA(dh) \
92
- Data_Wrap_Struct(cData, 0, gpgme_data_release, dh)
167
+ TypedData_Wrap_Struct(cData, &gpgme_data_type, dh)
93
168
  /* `gpgme_data_t' is typedef'ed as `struct gpgme_data *'. */
94
169
  #define UNWRAP_GPGME_DATA(vdh, dh) \
95
- Data_Get_Struct(vdh, struct gpgme_data, dh);
170
+ TypedData_Get_Struct(vdh, struct gpgme_data, &gpgme_data_type, dh)
96
171
 
97
172
  #define WRAP_GPGME_CTX(ctx) \
98
- Data_Wrap_Struct(cCtx, 0, gpgme_release, ctx)
173
+ TypedData_Wrap_Struct(cCtx, &gpgme_ctx_type, ctx)
99
174
  /* `gpgme_ctx_t' is typedef'ed as `struct gpgme_context *'. */
100
175
  #define UNWRAP_GPGME_CTX(vctx, ctx) \
101
- Data_Get_Struct(vctx, struct gpgme_context, ctx)
176
+ TypedData_Get_Struct(vctx, struct gpgme_context, &gpgme_ctx_type, ctx)
102
177
 
103
178
  #define WRAP_GPGME_KEY(key) \
104
- Data_Wrap_Struct(cKey, 0, gpgme_key_unref, key)
179
+ TypedData_Wrap_Struct(cKey, &gpgme_key_type, key)
105
180
  /* `gpgme_key_t' is typedef'ed as `struct _gpgme_key *'. */
106
181
  #define UNWRAP_GPGME_KEY(vkey, key) \
107
- Data_Get_Struct(vkey, struct _gpgme_key, key)
182
+ TypedData_Get_Struct(vkey, struct _gpgme_key, &gpgme_key_type, key)
108
183
 
109
184
  #if defined(GPGME_VERSION_NUMBER) && GPGME_VERSION_NUMBER < 0x020000
110
185
  #define WRAP_GPGME_TRUST_ITEM(item) \
111
- Data_Wrap_Struct(cTrustItem, 0, gpgme_trust_item_unref, item)
186
+ TypedData_Wrap_Struct(cTrustItem, &gpgme_trust_item_type, item)
112
187
  /* `gpgme_trust_item_t' is typedef'ed as `struct _gpgme_trust_item *'. */
113
188
  #define UNWRAP_GPGME_TRUST_ITEM(vitem, item) \
114
- Data_Get_Struct(vitem, struct _gpgme_trust_item, item)
189
+ TypedData_Get_Struct(vitem, struct _gpgme_trust_item, &gpgme_trust_item_type, item)
115
190
  #endif
116
191
 
117
192
  static VALUE cEngineInfo,
@@ -345,11 +420,21 @@ static ssize_t
345
420
  write_cb (void *handle, const void *buffer, size_t size)
346
421
  {
347
422
  VALUE vcb = (VALUE)handle, vcbs, vhook_value, vbuffer, vnwrite;
423
+ rb_encoding *enc;
348
424
 
349
425
  vcbs = RARRAY_PTR(vcb)[0];
350
426
  vhook_value = RARRAY_PTR(vcb)[1];
351
427
  vbuffer = rb_str_new (buffer, size);
352
428
 
429
+ /* Associate encoding with the buffer string.
430
+ * Use default internal encoding if set, otherwise use binary encoding.
431
+ * This allows the app author to control the encoding via
432
+ * Encoding.default_internal while maintaining backward compatibility. */
433
+ enc = rb_default_internal_encoding ();
434
+ if (!enc)
435
+ enc = rb_ascii8bit_encoding ();
436
+ rb_enc_associate (vbuffer, enc);
437
+
353
438
  vnwrite = rb_funcall (vcbs, rb_intern ("write"), 3,
354
439
  vhook_value, vbuffer, LONG2NUM(size));
355
440
  return NUM2LONG(vnwrite);
@@ -514,7 +599,7 @@ rb_s_gpgme_release (VALUE dummy, VALUE vctx)
514
599
  if (!ctx)
515
600
  rb_raise (rb_eArgError, "released ctx");
516
601
  gpgme_release (ctx);
517
- DATA_PTR(vctx) = NULL;
602
+ RTYPEDDATA_DATA(vctx) = NULL;
518
603
  return Qnil;
519
604
  }
520
605
 
@@ -1125,6 +1210,9 @@ rb_s_gpgme_op_keylist_next (VALUE dummy, VALUE vctx, VALUE rkey)
1125
1210
  save_gpgme_key_attrs (vkey, key);
1126
1211
  rb_ary_store (rkey, 0, vkey);
1127
1212
  }
1213
+
1214
+ RB_GC_GUARD(vctx);
1215
+
1128
1216
  return LONG2NUM(err);
1129
1217
  }
1130
1218
 
@@ -1540,6 +1628,305 @@ rb_s_gpgme_op_delete_start (VALUE dummy, VALUE vctx, VALUE vkey,
1540
1628
  return LONG2NUM(err);
1541
1629
  }
1542
1630
 
1631
+ /*
1632
+ * Key Edit Interface
1633
+ *
1634
+ * GPGME 2.0.0 deprecated gpgme_op_edit, gpgme_op_edit_start, gpgme_op_card_edit,
1635
+ * and gpgme_op_card_edit_start in favor of gpgme_op_interact and gpgme_op_interact_start.
1636
+ *
1637
+ * The new interact callback has a different signature:
1638
+ * Old: gpgme_error_t (*)(void *opaque, gpgme_status_code_t status, const char *args, int fd)
1639
+ * New: gpgme_error_t (*)(void *opaque, const char *keyword, const char *args, int fd)
1640
+ *
1641
+ * For backward compatibility, we keep the same Ruby interface (gpgme_op_edit, etc.)
1642
+ * but internally use gpgme_op_interact for GPGME 2.0.0+, converting keyword strings
1643
+ * to status codes in the callback shim.
1644
+ */
1645
+
1646
+ #if defined(GPGME_VERSION_NUMBER) && GPGME_VERSION_NUMBER >= 0x020000
1647
+ /*
1648
+ * GPGME 2.0.0+: Use gpgme_op_interact internally, but expose the same
1649
+ * gpgme_op_edit interface to Ruby for backward compatibility.
1650
+ *
1651
+ * The shim callback converts keyword strings to status codes.
1652
+ */
1653
+
1654
+ /* Macro to define keyword-to-status mapping entries */
1655
+ #define STATUS_ENTRY(x) { #x, GPGME_STATUS_##x }
1656
+
1657
+ /* Mapping table from keyword strings to status codes */
1658
+ static struct {
1659
+ const char *keyword;
1660
+ gpgme_status_code_t status;
1661
+ } keyword_to_status[] = {
1662
+ STATUS_ENTRY(EOF),
1663
+ STATUS_ENTRY(ENTER),
1664
+ STATUS_ENTRY(LEAVE),
1665
+ STATUS_ENTRY(ABORT),
1666
+ STATUS_ENTRY(GOODSIG),
1667
+ STATUS_ENTRY(BADSIG),
1668
+ STATUS_ENTRY(ERRSIG),
1669
+ STATUS_ENTRY(BADARMOR),
1670
+ STATUS_ENTRY(RSA_OR_IDEA),
1671
+ STATUS_ENTRY(KEYEXPIRED),
1672
+ STATUS_ENTRY(KEYREVOKED),
1673
+ STATUS_ENTRY(TRUST_UNDEFINED),
1674
+ STATUS_ENTRY(TRUST_NEVER),
1675
+ STATUS_ENTRY(TRUST_MARGINAL),
1676
+ STATUS_ENTRY(TRUST_FULLY),
1677
+ STATUS_ENTRY(TRUST_ULTIMATE),
1678
+ STATUS_ENTRY(SHM_INFO),
1679
+ STATUS_ENTRY(SHM_GET),
1680
+ STATUS_ENTRY(SHM_GET_BOOL),
1681
+ STATUS_ENTRY(SHM_GET_HIDDEN),
1682
+ STATUS_ENTRY(NEED_PASSPHRASE),
1683
+ STATUS_ENTRY(VALIDSIG),
1684
+ STATUS_ENTRY(SIG_ID),
1685
+ STATUS_ENTRY(ENC_TO),
1686
+ STATUS_ENTRY(NODATA),
1687
+ STATUS_ENTRY(BAD_PASSPHRASE),
1688
+ STATUS_ENTRY(NO_PUBKEY),
1689
+ STATUS_ENTRY(NO_SECKEY),
1690
+ STATUS_ENTRY(NEED_PASSPHRASE_SYM),
1691
+ STATUS_ENTRY(DECRYPTION_FAILED),
1692
+ STATUS_ENTRY(DECRYPTION_OKAY),
1693
+ STATUS_ENTRY(MISSING_PASSPHRASE),
1694
+ STATUS_ENTRY(GOOD_PASSPHRASE),
1695
+ STATUS_ENTRY(GOODMDC),
1696
+ STATUS_ENTRY(BADMDC),
1697
+ STATUS_ENTRY(ERRMDC),
1698
+ STATUS_ENTRY(IMPORTED),
1699
+ STATUS_ENTRY(IMPORT_OK),
1700
+ STATUS_ENTRY(IMPORT_PROBLEM),
1701
+ STATUS_ENTRY(IMPORT_RES),
1702
+ STATUS_ENTRY(FILE_START),
1703
+ STATUS_ENTRY(FILE_DONE),
1704
+ STATUS_ENTRY(FILE_ERROR),
1705
+ STATUS_ENTRY(BEGIN_DECRYPTION),
1706
+ STATUS_ENTRY(END_DECRYPTION),
1707
+ STATUS_ENTRY(BEGIN_ENCRYPTION),
1708
+ STATUS_ENTRY(END_ENCRYPTION),
1709
+ STATUS_ENTRY(DELETE_PROBLEM),
1710
+ STATUS_ENTRY(GET_BOOL),
1711
+ STATUS_ENTRY(GET_LINE),
1712
+ STATUS_ENTRY(GET_HIDDEN),
1713
+ STATUS_ENTRY(GOT_IT),
1714
+ STATUS_ENTRY(PROGRESS),
1715
+ STATUS_ENTRY(SIG_CREATED),
1716
+ STATUS_ENTRY(SESSION_KEY),
1717
+ STATUS_ENTRY(NOTATION_NAME),
1718
+ STATUS_ENTRY(NOTATION_DATA),
1719
+ STATUS_ENTRY(POLICY_URL),
1720
+ STATUS_ENTRY(BEGIN_STREAM),
1721
+ STATUS_ENTRY(END_STREAM),
1722
+ STATUS_ENTRY(KEY_CREATED),
1723
+ STATUS_ENTRY(USERID_HINT),
1724
+ STATUS_ENTRY(UNEXPECTED),
1725
+ STATUS_ENTRY(INV_RECP),
1726
+ STATUS_ENTRY(NO_RECP),
1727
+ STATUS_ENTRY(ALREADY_SIGNED),
1728
+ STATUS_ENTRY(SIGEXPIRED),
1729
+ STATUS_ENTRY(EXPSIG),
1730
+ STATUS_ENTRY(EXPKEYSIG),
1731
+ STATUS_ENTRY(TRUNCATED),
1732
+ STATUS_ENTRY(ERROR),
1733
+ STATUS_ENTRY(NEWSIG),
1734
+ STATUS_ENTRY(REVKEYSIG),
1735
+ STATUS_ENTRY(SIG_SUBPACKET),
1736
+ STATUS_ENTRY(NEED_PASSPHRASE_PIN),
1737
+ STATUS_ENTRY(SC_OP_FAILURE),
1738
+ STATUS_ENTRY(SC_OP_SUCCESS),
1739
+ STATUS_ENTRY(CARDCTRL),
1740
+ STATUS_ENTRY(BACKUP_KEY_CREATED),
1741
+ STATUS_ENTRY(PKA_TRUST_BAD),
1742
+ STATUS_ENTRY(PKA_TRUST_GOOD),
1743
+ STATUS_ENTRY(PLAINTEXT),
1744
+ STATUS_ENTRY(INV_SGNR),
1745
+ STATUS_ENTRY(NO_SGNR),
1746
+ STATUS_ENTRY(SUCCESS),
1747
+ STATUS_ENTRY(DECRYPTION_INFO),
1748
+ STATUS_ENTRY(PLAINTEXT_LENGTH),
1749
+ STATUS_ENTRY(MOUNTPOINT),
1750
+ STATUS_ENTRY(PINENTRY_LAUNCHED),
1751
+ STATUS_ENTRY(ATTRIBUTE),
1752
+ STATUS_ENTRY(BEGIN_SIGNING),
1753
+ STATUS_ENTRY(KEY_NOT_CREATED),
1754
+ STATUS_ENTRY(INQUIRE_MAXLEN),
1755
+ STATUS_ENTRY(FAILURE),
1756
+ STATUS_ENTRY(KEY_CONSIDERED),
1757
+ STATUS_ENTRY(TOFU_USER),
1758
+ STATUS_ENTRY(TOFU_STATS),
1759
+ STATUS_ENTRY(TOFU_STATS_LONG),
1760
+ STATUS_ENTRY(NOTATION_FLAGS),
1761
+ STATUS_ENTRY(DECRYPTION_COMPLIANCE_MODE),
1762
+ STATUS_ENTRY(VERIFICATION_COMPLIANCE_MODE),
1763
+ STATUS_ENTRY(CANCELED_BY_USER),
1764
+ { NULL, GPGME_STATUS_EOF }
1765
+ };
1766
+
1767
+ #undef STATUS_ENTRY
1768
+
1769
+ static gpgme_status_code_t
1770
+ keyword_to_status_code (const char *keyword)
1771
+ {
1772
+ size_t i;
1773
+
1774
+ if (!keyword)
1775
+ return GPGME_STATUS_EOF;
1776
+
1777
+ for (i = 0; keyword_to_status[i].keyword != NULL; i++)
1778
+ {
1779
+ if (strcmp (keyword, keyword_to_status[i].keyword) == 0)
1780
+ return keyword_to_status[i].status;
1781
+ }
1782
+
1783
+ /* Unknown keyword - return EOF as fallback */
1784
+ return GPGME_STATUS_EOF;
1785
+ }
1786
+
1787
+ /*
1788
+ * Shim callback that converts keyword strings to status codes
1789
+ * for backward compatibility with existing Ruby callbacks.
1790
+ */
1791
+ static gpgme_error_t
1792
+ edit_cb_shim (void *hook, const char *keyword, const char *args, int fd)
1793
+ {
1794
+ VALUE vcb = (VALUE) hook, veditfunc, vhook_value;
1795
+ gpgme_status_code_t status;
1796
+
1797
+ veditfunc = RARRAY_PTR (vcb)[0];
1798
+ vhook_value = RARRAY_PTR (vcb)[1];
1799
+
1800
+ status = keyword_to_status_code (keyword);
1801
+
1802
+ rb_funcall (veditfunc, rb_intern ("call"), 4, vhook_value, INT2FIX (status),
1803
+ args ? rb_str_new2 (args) : rb_str_new2 (""), INT2NUM (fd));
1804
+ return gpgme_err_make (GPG_ERR_SOURCE_USER_1, GPG_ERR_NO_ERROR);
1805
+ }
1806
+
1807
+ static VALUE
1808
+ rb_s_gpgme_op_edit (VALUE dummy, VALUE vctx, VALUE vkey,
1809
+ VALUE veditfunc, VALUE vhook_value, VALUE vout)
1810
+ {
1811
+ gpgme_ctx_t ctx;
1812
+ gpgme_key_t key;
1813
+ gpgme_data_t out = NULL;
1814
+ VALUE vcb;
1815
+ gpgme_error_t err;
1816
+
1817
+ CHECK_KEYLIST_NOT_IN_PROGRESS (vctx);
1818
+
1819
+ UNWRAP_GPGME_CTX(vctx, ctx);
1820
+ if (!ctx)
1821
+ rb_raise (rb_eArgError, "released ctx");
1822
+ UNWRAP_GPGME_KEY(vkey, key);
1823
+ if (!NIL_P(vout))
1824
+ UNWRAP_GPGME_DATA(vout, out);
1825
+
1826
+ vcb = rb_ary_new ();
1827
+ rb_ary_push (vcb, veditfunc);
1828
+ rb_ary_push (vcb, vhook_value);
1829
+ /* Keep a reference to avoid GC. */
1830
+ rb_iv_set (vctx, "@edit_cb", vcb);
1831
+
1832
+ /* Use gpgme_op_interact with flags=0, shim converts keywords to status codes */
1833
+ err = gpgme_op_interact (ctx, key, 0, edit_cb_shim, (void *)vcb, out);
1834
+ return LONG2NUM(err);
1835
+ }
1836
+
1837
+ static VALUE
1838
+ rb_s_gpgme_op_edit_start (VALUE dummy, VALUE vctx, VALUE vkey,
1839
+ VALUE veditfunc, VALUE vhook_value, VALUE vout)
1840
+ {
1841
+ gpgme_ctx_t ctx;
1842
+ gpgme_key_t key;
1843
+ gpgme_data_t out = NULL;
1844
+ VALUE vcb;
1845
+ gpgme_error_t err;
1846
+
1847
+ CHECK_KEYLIST_NOT_IN_PROGRESS(vctx);
1848
+
1849
+ UNWRAP_GPGME_CTX(vctx, ctx);
1850
+ if (!ctx)
1851
+ rb_raise (rb_eArgError, "released ctx");
1852
+ UNWRAP_GPGME_KEY(vkey, key);
1853
+ if (!NIL_P(vout))
1854
+ UNWRAP_GPGME_DATA(vout, out);
1855
+
1856
+ vcb = rb_ary_new ();
1857
+ rb_ary_push (vcb, veditfunc);
1858
+ rb_ary_push (vcb, vhook_value);
1859
+ /* Keep a reference to avoid GC. */
1860
+ rb_iv_set (vctx, "@edit_cb", vcb);
1861
+
1862
+ /* Use gpgme_op_interact_start with flags=0, shim converts keywords to status codes */
1863
+ err = gpgme_op_interact_start (ctx, key, 0, edit_cb_shim, (void *)vcb, out);
1864
+ return LONG2NUM(err);
1865
+ }
1866
+
1867
+ static VALUE
1868
+ rb_s_gpgme_op_card_edit (VALUE dummy, VALUE vctx, VALUE vkey,
1869
+ VALUE veditfunc, VALUE vhook_value, VALUE vout)
1870
+ {
1871
+ gpgme_ctx_t ctx;
1872
+ gpgme_key_t key;
1873
+ gpgme_data_t out = NULL;
1874
+ VALUE vcb;
1875
+ gpgme_error_t err;
1876
+
1877
+ CHECK_KEYLIST_NOT_IN_PROGRESS(vctx);
1878
+
1879
+ UNWRAP_GPGME_CTX(vctx, ctx);
1880
+ if (!ctx)
1881
+ rb_raise (rb_eArgError, "released ctx");
1882
+ UNWRAP_GPGME_KEY(vkey, key);
1883
+ if (!NIL_P(vout))
1884
+ UNWRAP_GPGME_DATA(vout, out);
1885
+
1886
+ vcb = rb_ary_new ();
1887
+ rb_ary_push (vcb, veditfunc);
1888
+ rb_ary_push (vcb, vhook_value);
1889
+ /* Keep a reference to avoid GC. */
1890
+ rb_iv_set (vctx, "@card_edit_cb", vcb);
1891
+
1892
+ /* Use gpgme_op_interact with GPGME_INTERACT_CARD flag */
1893
+ err = gpgme_op_interact (ctx, key, GPGME_INTERACT_CARD, edit_cb_shim, (void *)vcb, out);
1894
+ return LONG2NUM(err);
1895
+ }
1896
+
1897
+ static VALUE
1898
+ rb_s_gpgme_op_card_edit_start (VALUE dummy, VALUE vctx, VALUE vkey,
1899
+ VALUE veditfunc, VALUE vhook_value, VALUE vout)
1900
+ {
1901
+ gpgme_ctx_t ctx;
1902
+ gpgme_key_t key;
1903
+ gpgme_data_t out = NULL;
1904
+ VALUE vcb;
1905
+ gpgme_error_t err;
1906
+
1907
+ CHECK_KEYLIST_NOT_IN_PROGRESS(vctx);
1908
+
1909
+ UNWRAP_GPGME_CTX(vctx, ctx);
1910
+ if (!ctx)
1911
+ rb_raise (rb_eArgError, "released ctx");
1912
+ UNWRAP_GPGME_KEY(vkey, key);
1913
+ if (!NIL_P(vout))
1914
+ UNWRAP_GPGME_DATA(vout, out);
1915
+
1916
+ vcb = rb_ary_new ();
1917
+ rb_ary_push (vcb, veditfunc);
1918
+ rb_ary_push (vcb, vhook_value);
1919
+ /* Keep a reference to avoid GC. */
1920
+ rb_iv_set (vctx, "@card_edit_cb", vcb);
1921
+
1922
+ /* Use gpgme_op_interact_start with GPGME_INTERACT_CARD flag */
1923
+ err = gpgme_op_interact_start (ctx, key, GPGME_INTERACT_CARD, edit_cb_shim, (void *)vcb, out);
1924
+ return LONG2NUM(err);
1925
+ }
1926
+
1927
+ #else
1928
+ /* GPGME < 2.0.0: Use the original gpgme_op_edit functions directly */
1929
+
1543
1930
  static gpgme_error_t
1544
1931
  edit_cb (void *hook, gpgme_status_code_t status, const char *args, int fd)
1545
1932
  {
@@ -1669,6 +2056,7 @@ rb_s_gpgme_op_card_edit_start (VALUE dummy, VALUE vctx, VALUE vkey,
1669
2056
  err = gpgme_op_card_edit_start (ctx, key, edit_cb, (void *)vcb, out);
1670
2057
  return LONG2NUM(err);
1671
2058
  }
2059
+ #endif /* GPGME_VERSION_NUMBER >= 0x020000 */
1672
2060
 
1673
2061
  #if defined(GPGME_VERSION_NUMBER) && GPGME_VERSION_NUMBER < 0x020000
1674
2062
  static VALUE
@@ -1752,6 +2140,11 @@ rb_s_gpgme_op_decrypt (VALUE dummy, VALUE vctx, VALUE vcipher, VALUE vplain)
1752
2140
  UNWRAP_GPGME_DATA(vplain, plain);
1753
2141
 
1754
2142
  err = gpgme_op_decrypt (ctx, cipher, plain);
2143
+
2144
+ RB_GC_GUARD(vctx);
2145
+ RB_GC_GUARD(vcipher);
2146
+ RB_GC_GUARD(vplain);
2147
+
1755
2148
  return LONG2NUM(err);
1756
2149
  }
1757
2150
 
@@ -1831,6 +2224,12 @@ rb_s_gpgme_op_verify (VALUE dummy, VALUE vctx, VALUE vsig, VALUE vsigned_text,
1831
2224
  UNWRAP_GPGME_DATA(vplain, plain);
1832
2225
 
1833
2226
  err = gpgme_op_verify (ctx, sig, signed_text, plain);
2227
+
2228
+ RB_GC_GUARD(vctx);
2229
+ RB_GC_GUARD(vsig);
2230
+ RB_GC_GUARD(vsigned_text);
2231
+ RB_GC_GUARD(vplain);
2232
+
1834
2233
  return LONG2NUM(err);
1835
2234
  }
1836
2235
 
@@ -1935,6 +2334,11 @@ rb_s_gpgme_op_decrypt_verify (VALUE dummy, VALUE vctx, VALUE vcipher,
1935
2334
  UNWRAP_GPGME_DATA(vplain, plain);
1936
2335
 
1937
2336
  err = gpgme_op_decrypt_verify (ctx, cipher, plain);
2337
+
2338
+ RB_GC_GUARD(vctx);
2339
+ RB_GC_GUARD(vcipher);
2340
+ RB_GC_GUARD(vplain);
2341
+
1938
2342
  return LONG2NUM(err);
1939
2343
  }
1940
2344
 
@@ -2019,6 +2423,11 @@ rb_s_gpgme_op_sign (VALUE dummy, VALUE vctx, VALUE vplain, VALUE vsig,
2019
2423
  UNWRAP_GPGME_DATA(vsig, sig);
2020
2424
 
2021
2425
  err = gpgme_op_sign (ctx, plain, sig, NUM2INT(vmode));
2426
+
2427
+ RB_GC_GUARD(vctx);
2428
+ RB_GC_GUARD(vplain);
2429
+ RB_GC_GUARD(vsig);
2430
+
2022
2431
  return LONG2NUM(err);
2023
2432
  }
2024
2433
 
@@ -2128,6 +2537,12 @@ rb_s_gpgme_op_encrypt (VALUE dummy, VALUE vctx, VALUE vrecp, VALUE vflags,
2128
2537
  err = gpgme_op_encrypt (ctx, recp, NUM2INT(vflags), plain, cipher);
2129
2538
  if (recp)
2130
2539
  xfree (recp);
2540
+
2541
+ RB_GC_GUARD(vctx);
2542
+ RB_GC_GUARD(vrecp);
2543
+ RB_GC_GUARD(vplain);
2544
+ RB_GC_GUARD(vcipher);
2545
+
2131
2546
  return LONG2NUM(err);
2132
2547
  }
2133
2548
 
@@ -2227,6 +2642,12 @@ rb_s_gpgme_op_encrypt_sign (VALUE dummy, VALUE vctx, VALUE vrecp, VALUE vflags,
2227
2642
  err = gpgme_op_encrypt_sign (ctx, recp, NUM2INT(vflags), plain, cipher);
2228
2643
  if (recp)
2229
2644
  xfree (recp);
2645
+
2646
+ RB_GC_GUARD(vctx);
2647
+ RB_GC_GUARD(vrecp);
2648
+ RB_GC_GUARD(vplain);
2649
+ RB_GC_GUARD(vcipher);
2650
+
2230
2651
  return LONG2NUM(err);
2231
2652
  }
2232
2653
 
@@ -2395,7 +2816,7 @@ rb_s_gpgme_op_random_bytes (VALUE dummy, VALUE vctx, VALUE vsize, VALUE vmode)
2395
2816
  UNWRAP_GPGME_CTX(vctx, ctx);
2396
2817
  if (!ctx)
2397
2818
  rb_raise (rb_eArgError, "released ctx");
2398
-
2819
+
2399
2820
  size = NUM2SIZET(vsize);
2400
2821
  buffer = ALLOC_N(char, size);
2401
2822
 
@@ -2648,6 +3069,11 @@ Init_gpgme_n (void)
2648
3069
  rb_s_gpgme_op_delete_ext, 3);
2649
3070
  rb_define_module_function (mGPGME, "gpgme_op_delete_start",
2650
3071
  rb_s_gpgme_op_delete_start, 3);
3072
+
3073
+ /* Key Edit Interface
3074
+ * For GPGME 2.0.0+, these functions use gpgme_op_interact internally
3075
+ * with a shim to maintain backward compatibility with existing callbacks.
3076
+ */
2651
3077
  rb_define_module_function (mGPGME, "gpgme_op_edit",
2652
3078
  rb_s_gpgme_op_edit, 5);
2653
3079
  rb_define_module_function (mGPGME, "gpgme_op_edit_start",
@@ -3218,11 +3644,58 @@ Init_gpgme_n (void)
3218
3644
  /* The available flags for gpgme_op_encrypt. */
3219
3645
  rb_define_const (mGPGME, "GPGME_ENCRYPT_ALWAYS_TRUST",
3220
3646
  INT2FIX(GPGME_ENCRYPT_ALWAYS_TRUST));
3221
- /* This flag was added in 1.2.0. */
3222
- #ifdef GPGME_ENCRYPT_NO_ENCRYPT_TO
3647
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_NO_ENCRYPT_TO
3223
3648
  rb_define_const (mGPGME, "GPGME_ENCRYPT_NO_ENCRYPT_TO",
3224
3649
  INT2FIX(GPGME_ENCRYPT_NO_ENCRYPT_TO));
3225
3650
  #endif
3651
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_PREPARE
3652
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_PREPARE",
3653
+ INT2FIX(GPGME_ENCRYPT_PREPARE));
3654
+ #endif
3655
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_EXPECT_SIGN
3656
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_EXPECT_SIGN",
3657
+ INT2FIX(GPGME_ENCRYPT_EXPECT_SIGN));
3658
+ #endif
3659
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_NO_COMPRESS
3660
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_NO_COMPRESS",
3661
+ INT2FIX(GPGME_ENCRYPT_NO_COMPRESS));
3662
+ #endif
3663
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_UNSIGNED_INTEGRITY_CHECK
3664
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_UNSIGNED_INTEGRITY_CHECK",
3665
+ INT2FIX(GPGME_ENCRYPT_UNSIGNED_INTEGRITY_CHECK));
3666
+ #endif
3667
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_SYMMETRIC
3668
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_SYMMETRIC",
3669
+ INT2FIX(GPGME_ENCRYPT_SYMMETRIC));
3670
+ #endif
3671
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_THROW_KEYIDS
3672
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_THROW_KEYIDS",
3673
+ INT2FIX(GPGME_ENCRYPT_THROW_KEYIDS));
3674
+ #endif
3675
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_WRAP
3676
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_WRAP",
3677
+ INT2FIX(GPGME_ENCRYPT_WRAP));
3678
+ #endif
3679
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_WANT_ADDRESS
3680
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_WANT_ADDRESS",
3681
+ INT2FIX(GPGME_ENCRYPT_WANT_ADDRESS));
3682
+ #endif
3683
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_ARCHIVE
3684
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_ARCHIVE",
3685
+ INT2FIX(GPGME_ENCRYPT_ARCHIVE));
3686
+ #endif
3687
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_FILE
3688
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_FILE",
3689
+ INT2FIX(GPGME_ENCRYPT_FILE));
3690
+ #endif
3691
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_ADD_RECP
3692
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_ADD_RECP",
3693
+ INT2FIX(GPGME_ENCRYPT_ADD_RECP));
3694
+ #endif
3695
+ #ifdef HAVE_CONST_GPGME_ENCRYPT_CHG_RECP
3696
+ rb_define_const (mGPGME, "GPGME_ENCRYPT_CHG_RECP",
3697
+ INT2FIX(GPGME_ENCRYPT_CHG_RECP));
3698
+ #endif
3226
3699
 
3227
3700
  /* Random number generation mode flags added in 2.0.0 */
3228
3701
  #if defined(GPGME_VERSION_NUMBER) && GPGME_VERSION_NUMBER >= 0x020000
@@ -101,6 +101,24 @@ module GPGME
101
101
  if defined?(GPGME_ENCRYPT_NO_ENCRYPT_TO)
102
102
  ENCRYPT_NO_ENCRYPT_TO = GPGME_ENCRYPT_NO_ENCRYPT_TO
103
103
  end
104
+ if defined?(GPGME_ENCRYPT_PREPARE)
105
+ ENCRYPT_PREPARE = GPGME_ENCRYPT_PREPARE
106
+ end
107
+ if defined?(GPGME_ENCRYPT_EXPECT_SIGN)
108
+ ENCRYPT_EXPECT_SIGN = GPGME_ENCRYPT_EXPECT_SIGN
109
+ end
110
+ if defined?(GPGME_ENCRYPT_NO_COMPRESS)
111
+ ENCRYPT_NO_COMPRESS = GPGME_ENCRYPT_NO_COMPRESS
112
+ end
113
+ if defined?(GPGME_ENCRYPT_UNSIGNED_INTEGRITY_CHECK)
114
+ ENCRYPT_UNSIGNED_INTEGRITY_CHECK = GPGME_ENCRYPT_UNSIGNED_INTEGRITY_CHECK
115
+ end
116
+ if defined?(GPGME_ENCRYPT_SYMMETRIC)
117
+ ENCRYPT_SYMMETRIC = GPGME_ENCRYPT_SYMMETRIC
118
+ end
119
+ if defined?(GPGME_ENCRYPT_THROW_KEYIDS)
120
+ ENCRYPT_THROW_KEYIDS = GPGME_ENCRYPT_THROW_KEYIDS
121
+ end
104
122
  IMPORT_NEW = GPGME_IMPORT_NEW
105
123
  IMPORT_SECRET = GPGME_IMPORT_SECRET
106
124
  IMPORT_SIG = GPGME_IMPORT_SIG
data/lib/gpgme/crypto.rb CHANGED
@@ -91,7 +91,9 @@ module GPGME
91
91
  begin
92
92
  if options[:sign]
93
93
  if options[:signers]
94
- signers = Key.find(:public, options[:signers], :sign)
94
+ # Optimization: reuse recipient Key objects if signers match
95
+ # to avoid redundant key lookups
96
+ signers = resolve_keys_for_signing(options[:signers], keys)
95
97
  ctx.add_signer(*signers)
96
98
  end
97
99
  ctx.encrypt_sign(keys, plain_data, cipher_data, flags)
@@ -353,5 +355,58 @@ module GPGME
353
355
  end
354
356
  end
355
357
 
358
+ private
359
+
360
+ # Resolves keys for signing, reusing already-fetched recipient keys when possible
361
+ # to avoid redundant key lookups which can be slow.
362
+ #
363
+ # @param signers_input [Array, String, Key] The signer identifiers
364
+ # @param recipient_keys [Array<Key>, nil] Already-fetched recipient keys to check for reuse
365
+ # @return [Array<Key>] Keys suitable for signing
366
+ def resolve_keys_for_signing(signers_input, recipient_keys)
367
+ signers_input = [signers_input].flatten
368
+ recipient_keys ||= []
369
+
370
+ # Build a lookup hash of recipient keys by various identifiers
371
+ recipient_lookup = {}
372
+ recipient_keys.each do |key|
373
+ next unless key.is_a?(Key)
374
+ # Index by fingerprint, short key ID, and email
375
+ recipient_lookup[key.fingerprint] = key if key.fingerprint
376
+ recipient_lookup[key.fingerprint[-16..]] = key if key.fingerprint && key.fingerprint.length >= 16
377
+ recipient_lookup[key.fingerprint[-8..]] = key if key.fingerprint && key.fingerprint.length >= 8
378
+ if key.uids && !key.uids.empty?
379
+ key.uids.each do |uid|
380
+ recipient_lookup[uid.email] = key if uid.email && !uid.email.empty?
381
+ end
382
+ end
383
+ end
384
+
385
+ result = []
386
+ needs_lookup = []
387
+
388
+ signers_input.each do |signer|
389
+ case signer
390
+ when Key
391
+ # Already a key object, use directly if it can sign
392
+ result << signer if signer.usable_for?([:sign])
393
+ when String
394
+ # Check if we already have this key from recipients
395
+ if (existing = recipient_lookup[signer]) && existing.usable_for?([:sign])
396
+ result << existing
397
+ else
398
+ needs_lookup << signer
399
+ end
400
+ end
401
+ end
402
+
403
+ # Only do a Key.find for signers we couldn't resolve from recipients
404
+ unless needs_lookup.empty?
405
+ result += Key.find(:public, needs_lookup, :sign)
406
+ end
407
+
408
+ result
409
+ end
410
+
356
411
  end # module Crypto
357
412
  end # module GPGME
data/lib/gpgme/ctx.rb CHANGED
@@ -76,10 +76,12 @@ module GPGME
76
76
  end
77
77
 
78
78
  if block_given?
79
- begin
80
- yield ctx
81
- ensure
82
- GPGME::gpgme_release(ctx)
79
+ GPGME.synchronize do
80
+ begin
81
+ yield ctx
82
+ ensure
83
+ GPGME::gpgme_release(ctx)
84
+ end
83
85
  end
84
86
  else
85
87
  ctx
@@ -464,6 +466,9 @@ module GPGME
464
466
  alias delete delete_key
465
467
 
466
468
  # Edit attributes of the key in the local key ring.
469
+ #
470
+ # The callback receives (hook_value, status, args, fd) where status is
471
+ # a numeric status code (e.g., GPGME::GPGME_STATUS_GET_BOOL).
467
472
  def edit_key(key, editfunc, hook_value = nil, out = Data.new)
468
473
  err = GPGME::gpgme_op_edit(self, key, editfunc, hook_value, out)
469
474
  exc = GPGME::error_to_exception(err)
@@ -472,6 +477,9 @@ module GPGME
472
477
  alias edit edit_key
473
478
 
474
479
  # Edit attributes of the key on the card.
480
+ #
481
+ # The callback receives (hook_value, status, args, fd) where status is
482
+ # a numeric status code (e.g., GPGME::GPGME_STATUS_GET_BOOL).
475
483
  def edit_card_key(key, editfunc, hook_value = nil, out = Data.new)
476
484
  err = GPGME::gpgme_op_card_edit(self, key, editfunc, hook_value, out)
477
485
  exc = GPGME::error_to_exception(err)
@@ -612,10 +620,21 @@ keylist_mode=#{KEYLIST_MODE_NAMES[keylist_mode]}>"
612
620
  private
613
621
 
614
622
  def self.pass_function(pass, uid_hint, passphrase_info, prev_was_bad, fd)
623
+ # Write the passphrase directly using IO.for_fd. We set autoclose=false
624
+ # to prevent Ruby from closing the fd (which belongs to GPGME/gpg-agent).
625
+ # The IO object is used only within this method scope and not stored,
626
+ # so we also ensure it isn't prematurely collected by keeping a strong
627
+ # reference until we're done.
615
628
  io = IO.for_fd(fd, 'w')
616
629
  io.autoclose = false
617
- io.puts pass
618
- io.flush
630
+ begin
631
+ io.write "#{pass}\n"
632
+ io.flush
633
+ rescue => e
634
+ # If the fd has become invalid (e.g. agent communication error),
635
+ # re-raise as a more descriptive error.
636
+ raise e
637
+ end
619
638
  end
620
639
 
621
640
  end
@@ -9,7 +9,12 @@ module GPGME
9
9
  end
10
10
 
11
11
  def write(hook, buffer, length)
12
- @io.write(buffer[0 .. length])
12
+ data = buffer[0 .. length]
13
+ # Handle encoding conversion if the IO has a different encoding
14
+ if @io.respond_to?(:external_encoding) && @io.external_encoding
15
+ data = data.encode(@io.external_encoding, invalid: :replace, undef: :replace)
16
+ end
17
+ @io.write(data)
13
18
  end
14
19
 
15
20
  def seek(hook, offset, whence)
data/lib/gpgme/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  module GPGME
2
2
  # The version of GPGME ruby binding you are using
3
- VERSION = "2.0.25"
3
+ VERSION = "2.0.26"
4
4
  end
data/lib/gpgme.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'gpgme_n'
2
+ require 'monitor'
2
3
 
3
- # TODO without this call one can't GPGME::Ctx.new, find out why
4
+ # This call initializes the GPGME library and must happen before
5
+ # any GPGME operations (e.g. Ctx.new) can succeed.
4
6
  GPGME::gpgme_check_version(nil)
5
7
 
6
8
  require 'gpgme/constants'
@@ -19,8 +21,61 @@ require 'gpgme/engine'
19
21
  require 'gpgme/crypto'
20
22
 
21
23
  module GPGME
24
+
25
+ # Mutex for serializing GPGME operations when thread safety is enabled.
26
+ # While the underlying GPGME C library supports separate contexts in
27
+ # separate threads, the communication with gpg-agent over Unix domain
28
+ # sockets can produce "Bad file descriptor" errors under heavy concurrent
29
+ # load. Thread-safe mode is enabled by default and can be disabled
30
+ # if not needed (e.g. single-threaded applications).
31
+ #
32
+ # A Monitor is used instead of a Mutex because GPGME operations are
33
+ # reentrant — e.g. Crypto#sign calls Ctx.new, and within that block,
34
+ # Key.find calls Ctx.new again.
35
+ #
36
+ # @example Disable thread-safe mode for single-threaded apps
37
+ # GPGME.thread_safe = false
38
+ #
39
+ @thread_safe_mutex = Monitor.new
40
+ @thread_safe = true
41
+
22
42
  class << self
23
43
 
44
+ # Enable or disable thread-safe mode. Enabled by default. When
45
+ # enabled, all high-level GPGME operations (encrypt, decrypt, sign,
46
+ # verify, key listing, etc.) are serialized through a global monitor
47
+ # to prevent concurrent access to gpg-agent from causing "Bad file
48
+ # descriptor" errors. Disable for single-threaded apps if the
49
+ # synchronization overhead is undesirable.
50
+ #
51
+ # @param [Boolean] value false to disable thread-safe mode
52
+ attr_writer :thread_safe
53
+
54
+ # Returns true if thread-safe mode is enabled.
55
+ def thread_safe?
56
+ @thread_safe
57
+ end
58
+
59
+ # The mutex used for thread-safe serialization. Can be used directly
60
+ # if you need finer-grained control over locking.
61
+ #
62
+ # @example manual locking
63
+ # GPGME.synchronize do
64
+ # # multiple GPGME operations atomically
65
+ # end
66
+ attr_reader :thread_safe_mutex
67
+
68
+ # Execute a block with the GPGME mutex held if thread-safe mode is
69
+ # enabled. If thread-safe mode is disabled, the block is executed
70
+ # directly without locking.
71
+ def synchronize(&block)
72
+ if @thread_safe
73
+ @thread_safe_mutex.synchronize(&block)
74
+ else
75
+ yield
76
+ end
77
+ end
78
+
24
79
  # From the c extension
25
80
  alias pubkey_algo_name gpgme_pubkey_algo_name
26
81
  alias hash_algo_name gpgme_hash_algo_name
data/test/ctx_test.rb CHANGED
@@ -70,9 +70,6 @@ describe GPGME::Ctx do
70
70
  end
71
71
 
72
72
  it "should not segfault" do
73
- cipher = GPGME::Data.new(KEY_1_ENCRYPTED)
74
- ouput = GPGME::Data.new
75
-
76
73
  GPGME::Ctx.new do |ctx|
77
74
  assert_raises ArgumentError do
78
75
  ctx.decrypt_result
@@ -214,7 +211,7 @@ describe GPGME::Ctx do
214
211
 
215
212
  it "doesn't allow just any value" do
216
213
  assert_raises GPGME::Error::InvalidValue do
217
- ctx = GPGME::Ctx.new(:protocol => -200)
214
+ GPGME::Ctx.new(:protocol => -200)
218
215
  end
219
216
  end
220
217
  end
@@ -0,0 +1,65 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'test_helper'
3
+
4
+ describe 'GPGME Encryption Flags' do
5
+ it 'should expose ENCRYPT_ALWAYS_TRUST' do
6
+ assert_equal 1, GPGME::ENCRYPT_ALWAYS_TRUST
7
+ end
8
+
9
+ it 'should expose ENCRYPT_NO_ENCRYPT_TO if available' do
10
+ if defined?(GPGME::ENCRYPT_NO_ENCRYPT_TO)
11
+ assert GPGME::ENCRYPT_NO_ENCRYPT_TO.is_a?(Integer)
12
+ end
13
+ end
14
+
15
+ it 'should expose ENCRYPT_PREPARE if available' do
16
+ if defined?(GPGME::ENCRYPT_PREPARE)
17
+ assert GPGME::ENCRYPT_PREPARE.is_a?(Integer)
18
+ end
19
+ end
20
+
21
+ it 'should expose ENCRYPT_EXPECT_SIGN if available' do
22
+ if defined?(GPGME::ENCRYPT_EXPECT_SIGN)
23
+ assert GPGME::ENCRYPT_EXPECT_SIGN.is_a?(Integer)
24
+ end
25
+ end
26
+
27
+ it 'should expose ENCRYPT_NO_COMPRESS if available' do
28
+ if defined?(GPGME::ENCRYPT_NO_COMPRESS)
29
+ assert GPGME::ENCRYPT_NO_COMPRESS.is_a?(Integer)
30
+ end
31
+ end
32
+
33
+ it 'should expose ENCRYPT_UNSIGNED_INTEGRITY_CHECK if available' do
34
+ if defined?(GPGME::ENCRYPT_UNSIGNED_INTEGRITY_CHECK)
35
+ assert GPGME::ENCRYPT_UNSIGNED_INTEGRITY_CHECK.is_a?(Integer)
36
+ end
37
+ end
38
+
39
+ it 'should expose ENCRYPT_SYMMETRIC if available' do
40
+ if defined?(GPGME::ENCRYPT_SYMMETRIC)
41
+ assert GPGME::ENCRYPT_SYMMETRIC.is_a?(Integer)
42
+ end
43
+ end
44
+
45
+ it 'should expose ENCRYPT_THROW_KEYIDS if available' do
46
+ if defined?(GPGME::ENCRYPT_THROW_KEYIDS)
47
+ assert GPGME::ENCRYPT_THROW_KEYIDS.is_a?(Integer)
48
+ end
49
+ end
50
+
51
+ it 'should use different flag values for different flags' do
52
+ flags = []
53
+ flags << GPGME::ENCRYPT_ALWAYS_TRUST
54
+ flags << GPGME::ENCRYPT_NO_ENCRYPT_TO if defined?(GPGME::ENCRYPT_NO_ENCRYPT_TO)
55
+ flags << GPGME::ENCRYPT_PREPARE if defined?(GPGME::ENCRYPT_PREPARE)
56
+ flags << GPGME::ENCRYPT_EXPECT_SIGN if defined?(GPGME::ENCRYPT_EXPECT_SIGN)
57
+ flags << GPGME::ENCRYPT_NO_COMPRESS if defined?(GPGME::ENCRYPT_NO_COMPRESS)
58
+ flags << GPGME::ENCRYPT_UNSIGNED_INTEGRITY_CHECK if defined?(GPGME::ENCRYPT_UNSIGNED_INTEGRITY_CHECK)
59
+ flags << GPGME::ENCRYPT_SYMMETRIC if defined?(GPGME::ENCRYPT_SYMMETRIC)
60
+ flags << GPGME::ENCRYPT_THROW_KEYIDS if defined?(GPGME::ENCRYPT_THROW_KEYIDS)
61
+
62
+ # All flags should be unique
63
+ assert_equal flags.length, flags.uniq.length
64
+ end
65
+ end
@@ -0,0 +1,169 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'test_helper'
3
+ require 'stringio'
4
+
5
+ describe GPGME::IOCallbacks do
6
+ describe "encoding handling" do
7
+ it "writes binary data to binary IO without error" do
8
+ io = StringIO.new
9
+ io.set_encoding(Encoding::ASCII_8BIT)
10
+ callbacks = GPGME::IOCallbacks.new(io)
11
+
12
+ # Binary data with bytes that aren't valid UTF-8
13
+ binary_data = "\xC3\x28".b # Invalid UTF-8 sequence
14
+
15
+ callbacks.write(nil, binary_data, binary_data.bytesize)
16
+ io.rewind
17
+ assert_equal binary_data, io.read
18
+ end
19
+
20
+ it "writes UTF-8 data to UTF-8 IO without error" do
21
+ io = StringIO.new
22
+ io.set_encoding(Encoding::UTF_8)
23
+ callbacks = GPGME::IOCallbacks.new(io)
24
+
25
+ utf8_data = "Héllo Wörld! 日本語"
26
+
27
+ callbacks.write(nil, utf8_data.encode(Encoding::UTF_8), utf8_data.bytesize)
28
+ io.rewind
29
+ assert_equal utf8_data, io.read
30
+ end
31
+
32
+ it "handles encoding conversion when IO has different encoding" do
33
+ io = StringIO.new
34
+ io.set_encoding(Encoding::UTF_8)
35
+ callbacks = GPGME::IOCallbacks.new(io)
36
+
37
+ # ASCII-8BIT string with valid UTF-8 bytes
38
+ data = "Hello World".b
39
+
40
+ # Should not raise Encoding::UndefinedConversionError
41
+ callbacks.write(nil, data, data.bytesize)
42
+ io.rewind
43
+ assert_equal "Hello World", io.read
44
+ end
45
+
46
+ it "replaces invalid characters when converting encodings" do
47
+ io = StringIO.new
48
+ io.set_encoding(Encoding::UTF_8)
49
+ callbacks = GPGME::IOCallbacks.new(io)
50
+
51
+ # Invalid UTF-8 sequence in ASCII-8BIT string
52
+ invalid_data = "Hello\xC3\x28World".b
53
+
54
+ # Should not raise, should replace invalid chars
55
+ callbacks.write(nil, invalid_data, invalid_data.bytesize)
56
+ io.rewind
57
+ result = io.read
58
+ # The invalid sequence should be replaced
59
+ refute_nil result
60
+ assert result.valid_encoding?
61
+ end
62
+
63
+ it "reads data from IO" do
64
+ io = StringIO.new("test data")
65
+ callbacks = GPGME::IOCallbacks.new(io)
66
+
67
+ result = callbacks.read(nil, 9)
68
+ assert_equal "test data", result
69
+ end
70
+
71
+ it "seeks in IO" do
72
+ io = StringIO.new("test data")
73
+ callbacks = GPGME::IOCallbacks.new(io)
74
+
75
+ callbacks.read(nil, 4) # read "test"
76
+ pos = callbacks.seek(nil, 0, IO::SEEK_SET)
77
+ assert_equal 0, pos
78
+
79
+ result = callbacks.read(nil, 4)
80
+ assert_equal "test", result
81
+ end
82
+
83
+ it "returns current position for seek with offset 0 and SEEK_CUR" do
84
+ io = StringIO.new("test data")
85
+ callbacks = GPGME::IOCallbacks.new(io)
86
+
87
+ callbacks.read(nil, 5) # read "test "
88
+ pos = callbacks.seek(nil, 0, IO::SEEK_CUR)
89
+ assert_equal 5, pos
90
+ end
91
+ end
92
+
93
+ describe "integration with GPGME signing" do
94
+ before do
95
+ skip unless ensure_keys GPGME::PROTOCOL_OpenPGP
96
+ end
97
+
98
+ it "clearsigns UTF-8 data without encoding errors" do
99
+ utf8_text = "Héllo Wörld! Ünïcödé tëxt 日本語"
100
+
101
+ crypto = GPGME::Crypto.new
102
+ output = StringIO.new
103
+ output.set_encoding(Encoding::UTF_8)
104
+
105
+ # This should not raise Encoding::UndefinedConversionError
106
+ crypto.sign(utf8_text, mode: GPGME::SIG_MODE_CLEAR, output: output)
107
+
108
+ output.rewind
109
+ result = output.read
110
+ refute_empty result
111
+ assert result.include?("BEGIN PGP SIGNED MESSAGE")
112
+ end
113
+
114
+ it "signs UTF-8 data and outputs to default buffer without errors" do
115
+ utf8_text = "Ünïcödé tëxt: äöü ÄÖÜ ß"
116
+
117
+ crypto = GPGME::Crypto.new
118
+ signed = crypto.sign(utf8_text)
119
+
120
+ result = signed.read
121
+ refute_empty result
122
+ end
123
+
124
+ it "encrypts and decrypts UTF-8 data correctly" do
125
+ utf8_text = "Sëcrét mëssägé with spëcïäl chäräctërs: 日本語"
126
+
127
+ crypto = GPGME::Crypto.new(always_trust: true)
128
+ encrypted = crypto.encrypt(utf8_text, recipients: KEYS.first[:sha])
129
+ decrypted = crypto.decrypt(encrypted)
130
+
131
+ result = decrypted.read
132
+ # Force UTF-8 encoding since GPGME returns binary data
133
+ result.force_encoding(Encoding::UTF_8)
134
+ assert_equal utf8_text, result
135
+ end
136
+ end
137
+
138
+ describe "default internal encoding support" do
139
+ it "respects Encoding.default_internal when set" do
140
+ # Save original setting
141
+ original_internal = Encoding.default_internal
142
+ original_verbose = $VERBOSE
143
+
144
+ begin
145
+ # Suppress warning about setting Encoding.default_internal
146
+ $VERBOSE = nil
147
+ Encoding.default_internal = Encoding::UTF_8
148
+ $VERBOSE = original_verbose
149
+
150
+ io = StringIO.new
151
+ io.set_encoding(Encoding::UTF_8)
152
+ callbacks = GPGME::IOCallbacks.new(io)
153
+
154
+ # Valid UTF-8 data
155
+ utf8_data = "Tëst dätä"
156
+ callbacks.write(nil, utf8_data, utf8_data.bytesize)
157
+
158
+ io.rewind
159
+ result = io.read
160
+ assert_equal utf8_data, result
161
+ ensure
162
+ # Restore original settings
163
+ $VERBOSE = nil
164
+ Encoding.default_internal = original_internal
165
+ $VERBOSE = original_verbose
166
+ end
167
+ end
168
+ end
169
+ end
metadata CHANGED
@@ -1,12 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gpgme
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.25
4
+ version: 2.0.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daiki Ueno
8
8
  - Albert Llop
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
11
  date: 2025-07-26 00:00:00.000000000 Z
@@ -137,10 +136,12 @@ files:
137
136
  - test/crypto_test.rb
138
137
  - test/ctx_test.rb
139
138
  - test/data_test.rb
139
+ - test/encryption_flags_test.rb
140
140
  - test/files/testkey_pub.gpg
141
141
  - test/files/testkey_pub_invalid.gpg
142
142
  - test/files/testkey_sec.gpg
143
143
  - test/gpgme_test.rb
144
+ - test/io_callbacks_test.rb
144
145
  - test/key_test.rb
145
146
  - test/pinentry
146
147
  - test/signature_test.rb
@@ -151,7 +152,6 @@ homepage: http://github.com/ueno/ruby-gpgme
151
152
  licenses:
152
153
  - LGPL-2.1+
153
154
  metadata: {}
154
- post_install_message:
155
155
  rdoc_options: []
156
156
  require_paths:
157
157
  - lib
@@ -167,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
167
  - !ruby/object:Gem::Version
168
168
  version: '0'
169
169
  requirements: []
170
- rubygems_version: 3.5.22
171
- signing_key:
170
+ rubygems_version: 3.7.2
172
171
  specification_version: 4
173
172
  summary: Ruby binding of GPGME.
174
173
  test_files: []