【问题标题】:How to get MIME encoded base64 string back to writeable byte array?如何将 MIME 编码的 base64 字符串恢复为可写字节数组?
【发布时间】:2018-08-28 09:59:17
【问题描述】:

我在数据库中有许多文件(PDF、doc、docx、jpg 等),它们是使用以下方法添加的:

附件以 MIME 编码(base64 编码)发送到数据库 字符串。然后组件转换这个 MIME 编码的字符串(在 unicode) 到字节流,然后将其作为 BLOB 写入数据库 (Oracle) 或映像 (SQL Server)。

字符串开头还附加了一个“guid”,长度为 76 个字符。

我正在尝试提取附件并将​​其保存到磁盘上的文件中,而不是数据库中。它的工作时间约为 20%。在将我的字符串传递给FromBase64String 时,我得到System.FormatException: Invalid character in a Base-64 string. 的其余时间。

我注意到数据库中的值如下所示,保存成功:

0x7B00350030003100460032003300350046002D00370

失败的总是这样开始的:

0x7B35303146323335462D373546302D343936342D394

我在这里没有足够的字符来粘贴完整的示例,因此请参阅this pastebin 以获取不起作用的示例。它应该代表一个 Word 文档,上面写着“仅测试文档”。 This one 是同一个文档,但转换为 PDF。

This 可以工作并转换为test font.htm。它必须插入到 SQL 数据库中的 image 列中,然后用我的代码拉出:

 private const int guidLength = 38 * 2;
 public static byte[] GetAttachment(string folderid, string filename) {
 string queryString = string.Format("SELECT <image column> FROM AttachmentTable WHERE .....",
                      folderid, filename);
                using (SqlConnection connection = new SqlConnection("context connection=true"))
                {
                    connection.Open();
                    using (SqlCommand selectAttachment = new SqlCommand(
                        queryString,
                        connection))
                    {
                        using (SqlDataReader reader = selectAttachment.ExecuteReader())                        {
                            while (reader.Read())
                            {
                                if (reader[0] == System.DBNull.Value)
                                    return new byte[0];
                                byte[] data = (byte[])reader[0];
                                byte[] truncatedData;
                                if (data[data.Length - 2] == 0)
                                    truncatedData = new byte[data.Length - guidLength - 2];
                                else
                                    truncatedData = new byte[data.Length - guidLength];
                                Array.Copy(data, guidLength, truncatedData, 0, truncatedData.Length);
                                // base64 unencode
                                string truncatedString = Encoding.Unicode.GetString(truncatedData);
                                return Convert.FromBase64String(truncatedString);
                            }
                        }

                    }
                } 

             }

然后保存附件:

 public static void SaveAttachmentToFile(string file, string folderid, string fileName)
        {
            byte[] data = GetAttachment(file, folderid);
            if (data == null)
                throw new ArgumentNullException("Attachment has no data, it may have been deleted");
            using (FileStream writer = new FileStream(fileName, FileMode.Create))
            {
                writer.Write(data, 0, data.Length);
            }
        }

SQL CLR 函数

   [SqlFunction(IsDeterministic = true,
                     IsPrecise = true,
                     DataAccess = DataAccessKind.Read,
                     SystemDataAccess = SystemDataAccessKind.Read)]
    public static SqlString WriteToFile(SqlString path, SqlString folderid, SqlString fileName)
    {
        try
        {
            if (!path.IsNull && !folderid.IsNull && !fileName.IsNull)
            {
                var dir = Path.GetDirectoryName(path.Value);
                if (!Directory.Exists(dir))
                    Directory.CreateDirectory(dir);
                string filename = Convert.ToString(fileName);
                string folderid = Convert.ToString(efolderid);
                string filepath = Convert.ToString(path);
                SaveAttachmentToFile(filename, folderid, filepath);
                return "Wrote file";
            }
            else
                return "No data passed to method!";
        }
        catch (IOException e)
        {
            return "Make sure the assembly has external access!\n" + e.ToString();
        }
        catch (Exception ex)
        {
            return ex.ToString();
        }
    }

注意,上面所有的 C# 代码都被编译成一个程序集,然后用作 CLR 函数:

CREATE FUNCTION [dbo].[WriteToFile](@path [nvarchar](max), @efolderid [nvarchar](max), @filename [nvarchar](max))
RETURNS [nvarchar](max) WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [ClassLibrary1].[CLR.UserDefinedFunctions].[WriteToFile]
GO

我认为我的问题可能与编码有关。我以为我可以使用Encoding.MIME.GetString,但它不存在。我也尝试过UTF-8,但成功率为 0%。 Unicode 似乎可以工作,但如上所述,成功率约为 20%。

我的问题是,为什么其中一些无法保存(不正确的 base64 字符.. 但为什么?)而其他的却可以正常工作?如何确定要使用的正确编码?它有一个模式,但我真的不知道如何从这里开始。

【问题讨论】:

  • 那些对我来说看起来不像 base64 字符串。它们看起来像是由十六进制对表示的字节序列。
  • 也许,但其中一些确实转换为 base64,然后我可以将文件保存到磁盘
  • 这并不意味着您最终会得到任何类型的有效数据。尝试在开始时截断 0x,然后每两个迭代它们并将它们从十六进制字符串转换为字节值,并保存生成的字节数组。
  • @Nyerguds Nope - 不太确定如何处理。你对如何实现byte value from hex string 部分有一个粗略的想法,比如,它是一个内置的.net 函数还是我需要自己编写的东西?
  • 你的问题有点不清楚,“0x”格式只是你用来检查数据库的任何东西显示的数据,而不是你实际的得到程序中的数据。虽然通过仔细阅读代码本身肯定可以推断出这一点,所以我为我的粗鲁道歉。

标签: c# encoding base64


【解决方案1】:

给定的数据插入方式不明确; “unicode”实际上不是文本编码;它是将符号表示为数字的一般系统。 .Net框架确实有一个编码叫“Unicode”,但是这个用词不当,这个编码其实是UTF-16。

现在,如上所述,您的数据有两种格式;一种有效,一种无效。这两种格式之间的区别在于,其中一种格式在每个数据字节之间都有00 字节。这对应于 UTF-16-LE,其中所有符号都是 16 位,也就是 2 个字节,值的最低部分存储在第一个字节中。没有那些 00 字节的压缩数据应该是纯 ASCII。

这种 UTF-16 格式实际上是一种非常愚蠢的保存 Base64 数据的方式,因为根据定义,Base64始终是纯 7 位 ascii;这些额外的字节将永远不会被使用,并且只是将保存该数据所需的空间增加一倍。事实上,当保存为字节时,Base64 编码也没有任何优点,因为 Base64 的目的是将二进制数据转换为纯文本,以便无法处理存储/传输二进制数据的系统处理它。鉴于此 Base64 文本随后在您的数据库中保存为二进制 LOB,这显然不是这里的情况。

除此之外,00 字节 do 在这里为您的问题提供了解决方案:正如我所说,对于 Base64 内容,这些中间字节将永远不会被使用,这意味着它们将 永远是00。另一方面,Base64 始终是纯 ASCII 文本,并且应该从不包含 00 字节。这意味着您可以检查那些 00 字节并使用它们的存在来选择正确的编码。

请注意,在将字节转换为字符串之后 切断 GUID 要简单得多,因为这样它的长度总是 38,而不是 ASCII 中的 38 个字节或UTF-16 76 字节。

使您的第一个代码块的阅读器部分适应此问题应该可以解决问题:

using (SqlDataReader reader = selectAttachment.ExecuteReader())
{
    // only reading one anyway; doesn't need to be a 'while'.
    if (!reader.Read())
        return new byte[0];
    if (reader[0] == System.DBNull.Value)
        return new byte[0];
    byte[] data = (byte[])reader[0];
    if (data.Length == 0)
        return new byte[0];
    String base64String
    if (data.Length > 1 && data[1] == 00)
        base64String = Encoding.Unicode.GetString(data);
    else
        base64String = Encoding.ASCII.GetString(data);
    // Cuts off the GUID, and takes care of any trailing 00 bytes.
    String truncatedString = base64String.Substring(38).TrimEnd('\0');
    return Convert.FromBase64String(truncatedString);
}

【讨论】:

  • 非常感谢,这非常有效,我很欣赏详细的解释。昨天我很生气,因为这让我非常沮丧,为激进的评论道歉。事实上,我自己从来没有想过这一点,所以我欠你一个。 :)
  • 是的,关于 0x 的东西有些混乱......我盲目地遵循已经发布的评论,而没有真正彻底阅读代码。对此感到抱歉。
  • 一切都好。我很感激花时间在这方面提供帮助。
  • 在将您的两个十六进制样本转储到在线十六进制到文本转换器中后,我实际上发现了问题,并注意到生成的文本(作为这些 guid 的开头)对于两者来说都是相同的,但是对于工作样本,尽管输入的大小相同,但转换后的字符串要短得多。就在那时我注意到了 00 字节,因为它们不是有效的字母,所以转换器简单地将其丢弃。
猜你喜欢
  • 2010-10-26
  • 2017-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-31
  • 2016-10-21
  • 2020-05-17
  • 1970-01-01
相关资源
最近更新 更多