static bool PrintGeneralName(const BIOPointer& out, const GENERAL_NAME* gen) {
  if (gen->type == GEN_DNS) {
    ASN1_IA5STRING* name = gen->d.dNSName;
    BIO_write(out.get(), "DNS:", 4);
    // Note that the preferred name syntax (see RFCs 5280 and 1034) with
    // wildcards is a subset of what we consider "safe", so spec-compliant DNS
    // names will never need to be escaped.
    PrintLatin1AltName(out, name);
  } else if (gen->type == GEN_EMAIL) {
    ASN1_IA5STRING* name = gen->d.rfc822Name;
    BIO_write(out.get(), "email:", 6);
    PrintLatin1AltName(out, name);
  } else if (gen->type == GEN_URI) {
    ASN1_IA5STRING* name = gen->d.uniformResourceIdentifier;
    BIO_write(out.get(), "URI:", 4);
    // The set of "safe" names was designed to include just about any URI,
    // with a few exceptions, most notably URIs that contains commas (see
    // RFC 2396). In other words, most legitimate URIs will not require
    // escaping.
    PrintLatin1AltName(out, name);
  } else if (gen->type == GEN_DIRNAME) {
    // For backward compatibility, use X509_NAME_oneline to print the
    // X509_NAME object. The format is non standard and should be avoided
    // elsewhere, but conveniently, the function produces ASCII and the output
    // is unlikely to contains commas or other characters that would require
    // escaping. With that in mind, note that it SHOULD NOT produce ASCII
    // output since an RFC5280 AttributeValue may be a UTF8String.
    // TODO(tniessen): switch to RFC2253 rules in a major release
    BIO_printf(out.get(), "DirName:");
    char oline[256];
    if (X509_NAME_oneline(gen->d.dirn, oline, sizeof(oline)) != nullptr) {
      PrintAltName(out, oline, strlen(oline), false, nullptr);
    } else {
      return false;
    }
  } else if (gen->type == GEN_IPADD) {
    BIO_printf(out.get(), "IP Address:");
    const ASN1_OCTET_STRING* ip = gen->d.ip;
    const unsigned char* b = ip->data;
    if (ip->length == 4) {
      BIO_printf(out.get(), "%d.%d.%d.%d", b[0], b[1], b[2], b[3]);
    } else if (ip->length == 16) {
      for (unsigned int j = 0; j < 8; j++) {
        uint16_t pair = (b[2 * j] << 8) | b[2 * j + 1];
        BIO_printf(out.get(), (j == 0) ? "%X" : ":%X", pair);
      }
    } else {
#if OPENSSL_VERSION_MAJOR >= 3
      BIO_printf(out.get(), "<invalid length=%d>", ip->length);
#else
      BIO_printf(out.get(), "<invalid>");
#endif
    }
  } else if (gen->type == GEN_RID) {
    // TODO(tniessen): unlike OpenSSL's default implementation, never print the
    // OID as text and instead always print its numeric representation, which is
    // backward compatible in practice and more future proof (see OBJ_obj2txt).
    char oline[256];
    i2t_ASN1_OBJECT(oline, sizeof(oline), gen->d.rid);
    BIO_printf(out.get(), "Registered ID:%s", oline);
  } else if (gen->type == GEN_OTHERNAME) {
    // TODO(tniessen): the format that is used here is based on OpenSSL's
    // implementation of i2v_GENERAL_NAME (as of OpenSSL 3.0.1), mostly for
    // backward compatibility. It is somewhat awkward, especially when passed to
    // translatePeerCertificate, and should be changed in the future, probably
    // to the format used by GENERAL_NAME_print (in a major release).
    bool unicode = true;
    const char* prefix = nullptr;
    // OpenSSL 1.1.1 does not support othername in i2v_GENERAL_NAME and may not
    // define these NIDs.
#if OPENSSL_VERSION_MAJOR >= 3
    int nid = OBJ_obj2nid(gen->d.otherName->type_id);
    switch (nid) {
      case NID_id_on_SmtpUTF8Mailbox:
        prefix = " SmtpUTF8Mailbox:";
        break;
      case NID_XmppAddr:
        prefix = " XmppAddr:";
        break;
      case NID_SRVName:
        prefix = " SRVName:";
        unicode = false;
        break;
      case NID_ms_upn:
        prefix = " UPN:";
        break;
      case NID_NAIRealm:
        prefix = " NAIRealm:";
        break;
    }
#endif  // OPENSSL_VERSION_MAJOR >= 3
    int val_type = gen->d.otherName->value->type;
    if (prefix == nullptr ||
        (unicode && val_type != V_ASN1_UTF8STRING) ||
        (!unicode && val_type != V_ASN1_IA5STRING)) {
      BIO_printf(out.get(), "othername:<unsupported>");
    } else {
      BIO_printf(out.get(), "othername:");
      if (unicode) {
        PrintUtf8AltName(out, gen->d.otherName->value->value.utf8string,
                         prefix);
      } else {
        PrintLatin1AltName(out, gen->d.otherName->value->value.ia5string,
                           prefix);
      }
    }
  } else if (gen->type == GEN_X400) {
    // TODO(tniessen): this is what OpenSSL does, implement properly instead
    BIO_printf(out.get(), "X400Name:<unsupported>");
  } else if (gen->type == GEN_EDIPARTY) {
    // TODO(tniessen): this is what OpenSSL does, implement properly instead
    BIO_printf(out.get(), "EdiPartyName:<unsupported>");
  } else {
    // This is safe because X509V3_EXT_d2i would have returned nullptr in this
    // case already.
    UNREACHABLE();
  }

  return true;
}