@@ -254,12 +254,17 @@ ossl_to_der_if_possible(VALUE obj)
254254/*
255255 * Errors
256256 */
257+ static ID id_i_errors ;
258+
259+ static void collect_errors_into (VALUE ary );
260+
257261VALUE
258262ossl_make_error (VALUE exc , VALUE str )
259263{
260264 unsigned long e ;
261265 const char * data ;
262266 int flags ;
267+ VALUE errors = rb_ary_new ();
263268
264269 if (NIL_P (str ))
265270 str = rb_str_new (NULL , 0 );
@@ -276,10 +281,12 @@ ossl_make_error(VALUE exc, VALUE str)
276281 rb_str_cat_cstr (str , msg ? msg : "(null)" );
277282 if (flags & ERR_TXT_STRING && data )
278283 rb_str_catf (str , " (%s)" , data );
279- ossl_clear_error ( );
284+ collect_errors_into ( errors );
280285 }
281286
282- return rb_exc_new_str (exc , str );
287+ VALUE obj = rb_exc_new_str (exc , str );
288+ rb_ivar_set (obj , id_i_errors , errors );
289+ return obj ;
283290}
284291
285292void
@@ -300,13 +307,12 @@ ossl_raise(VALUE exc, const char *fmt, ...)
300307 rb_exc_raise (ossl_make_error (exc , err ));
301308}
302309
303- void
304- ossl_clear_error ( void )
310+ static void
311+ collect_errors_into ( VALUE ary )
305312{
306- if (dOSSL == Qtrue ) {
313+ if (dOSSL == Qtrue || ! NIL_P ( ary ) ) {
307314 unsigned long e ;
308315 const char * file , * data , * func , * lib , * reason ;
309- char append [256 ] = "" ;
310316 int line , flags ;
311317
312318#ifdef HAVE_ERR_GET_ERROR_ALL
@@ -318,20 +324,66 @@ ossl_clear_error(void)
318324 lib = ERR_lib_error_string (e );
319325 reason = ERR_reason_error_string (e );
320326
327+ VALUE str = rb_sprintf ("error:%08lX:%s:%s:%s" , e , lib ? lib : "" ,
328+ func ? func : "" , reason ? reason : "" );
321329 if (flags & ERR_TXT_STRING ) {
322330 if (!data )
323331 data = "(null)" ;
324- snprintf ( append , sizeof ( append ) , " (%s)" , data );
332+ rb_str_catf ( str , " (%s)" , data );
325333 }
326- rb_warn ("error on stack: error:%08lX:%s:%s:%s%s" , e , lib ? lib : "" ,
327- func ? func : "" , reason ? reason : "" , append );
334+
335+ if (dOSSL == Qtrue )
336+ rb_warn ("error on stack: %" PRIsVALUE , str );
337+ if (!NIL_P (ary ))
338+ rb_ary_push (ary , str );
328339 }
329340 }
330341 else {
331342 ERR_clear_error ();
332343 }
333344}
334345
346+ void
347+ ossl_clear_error (void )
348+ {
349+ collect_errors_into (Qnil );
350+ }
351+
352+ /*
353+ * call-seq:
354+ * ossl_error.detailed_message(**) -> string
355+ *
356+ * Returns the exception message decorated with the captured \OpenSSL error
357+ * queue entries.
358+ */
359+ static VALUE
360+ osslerror_detailed_message (int argc , VALUE * argv , VALUE self )
361+ {
362+ VALUE str ;
363+ #ifdef HAVE_RB_CALL_SUPER_KW
364+ // Ruby >= 3.2
365+ if (RTEST (rb_funcall (rb_eException , rb_intern ("method_defined?" ), 1 ,
366+ ID2SYM (rb_intern ("detailed_message" )))))
367+ str = rb_call_super_kw (argc , argv , RB_PASS_CALLED_KEYWORDS );
368+ else
369+ #endif
370+ str = rb_funcall (self , rb_intern ("message" ), 0 );
371+ VALUE errors = rb_attr_get (self , id_i_errors );
372+
373+ // OpenSSLError was not created by ossl_make_error()
374+ if (!RB_TYPE_P (errors , T_ARRAY ))
375+ return str ;
376+
377+ str = rb_str_resurrect (str );
378+ rb_str_catf (str , "\nOpenSSL error queue reported %ld errors:" ,
379+ RARRAY_LEN (errors ));
380+ for (long i = 0 ; i < RARRAY_LEN (errors ); i ++ ) {
381+ VALUE err = RARRAY_AREF (errors , i );
382+ rb_str_catf (str , "\n%" PRIsVALUE , err );
383+ }
384+ return str ;
385+ }
386+
335387/*
336388 * call-seq:
337389 * OpenSSL.errors -> [String...]
@@ -1009,10 +1061,26 @@ Init_openssl(void)
10091061
10101062 rb_global_variable (& eOSSLError );
10111063 /*
1012- * Generic error,
1013- * common for all classes under OpenSSL module
1064+ * Generic error class for OpenSSL. All error classes in this library
1065+ * inherit from this class.
1066+ *
1067+ * This class indicates that an error was reported by the underlying
1068+ * \OpenSSL library.
1069+ */
1070+ eOSSLError = rb_define_class_under (mOSSL , "OpenSSLError" , rb_eStandardError );
1071+ /*
1072+ * \OpenSSL error queue entries captured at the time the exception was
1073+ * raised. The same information is printed to stderr if OpenSSL.debug is
1074+ * set to +true+.
1075+ *
1076+ * This is an array of zero or more strings, ordered from the oldest to the
1077+ * newest. The format of the strings is not stable and may vary across
1078+ * versions of \OpenSSL or versions of this Ruby extension.
1079+ *
1080+ * See also the man page ERR_get_error(3).
10141081 */
1015- eOSSLError = rb_define_class_under (mOSSL ,"OpenSSLError" ,rb_eStandardError );
1082+ rb_attr (eOSSLError , rb_intern_const ("errors" ), 1 , 0 , 0 );
1083+ rb_define_method (eOSSLError , "detailed_message" , osslerror_detailed_message , -1 );
10161084
10171085 /*
10181086 * Init debug core
@@ -1028,6 +1096,7 @@ Init_openssl(void)
10281096 * Get ID of to_der
10291097 */
10301098 ossl_s_to_der = rb_intern ("to_der" );
1099+ id_i_errors = rb_intern ("@errors" );
10311100
10321101 /*
10331102 * Init components
0 commit comments