【问题标题】:Including control characters in a .txt file to be read by C#在 C# 读取的 .txt 文件中包含控制字符
【发布时间】:2016-07-29 19:53:44
【问题描述】:

我正在开发一个使用纯 ASCII .txt 文件作为键/值配置文件的项目。 ConfigFile.txt 的当前格式类似于

FirstName=Elmer|LastName=Fudd|UserId=EFudd|Password=fubar|Date=7/29/2016

这很容易读入程序并使用 KeyValuePairs 创建一个字典,代码如下:

   using (FileStream fs = new FileStream("ConfigFile.txt", FileMode.Open))
    {
      using (StreamReader sr = new StreamReader(fs))
      {
        string fileText = sr.ReadToEnd();

        //  Tokenize the entire file string into separate key=value strings.
        string[] tokens = fileText.Split('|');

        //  Iterate through all of the key=value strings, tokenize each one into a key=Value 
        //  pair and add the key and value as separate strings into the dictionary.
        foreach (string token in tokens)
        {
          string[] keyValuePair = token.Split('=');
          configDict.Add(keyValuePair[0], keyValuePair[1]);
        }
      }
    }

它首先使用“|”将每个键/值拆分为单独的字符串作为分隔符。

名字=埃尔默

姓氏=法德

UserId=EFudd

密码=foobar

日期=2016 年 7 月 29 日

然后,对于每个键/值字符串,它在 '=' 分隔符上分隔键和值,创建一个 KeyValuePair,并将其插入到字典中以供以后在程序中查找。

到目前为止一切顺利。指示用户不要使用任何一个分隔符创建密码。但是,我现在必须在将密码包含在文件中之前对其进行加密,并且加密例程可以生成从 0x20 到 0x7F 的任何可打印字符。因此,加密的密码可以以一个或两个分隔符结尾。我最终可以通过加密引擎将“foobar”(或其他)加密为 P#|=g%。这会破坏 split 函数正常工作的能力。

所以,我想更改输入到记事本 .txt 文件中的分隔符来控制字符,而不是使用“|”分隔符,我使用 0x1E(记录分隔符)并将“=”符号替换为 0x1F(单位分隔符)。

我可以毫无问题地直接在 C# 中转义和编码,但我将如何修改原始 .txt 磁盘文件,以便它在分隔符中正确读取为单个(不可打印)字符?

【问题讨论】:

  • 为什么密码是明文?使用哈希,然后使用 base64 或 hex 对二进制文件进行编码。
  • 至于如何读取文件,就像您当前正在读取文件一样。如果您想发疯,您可以访问原始文件流,但这比在 SO 上解释要费力。
  • 我继承了明文密码。在将加密文本手动输入配置文件之前,我编写了一个实用程序来对它们进行加密,因此磁盘或内存上没有明文。应用程序在使用它们之前使用相同的加密来解密它们。
  • 使用 JSON 来存储这样的东西,甚至是 XML。
  • 我理解这个问题,但我认为您低估了编码和解码控制字符的内容。可能有一个实用程序可以为您执行此操作,但我找到的最接近的是Regex.Unescape,它适用于正则表达式语法,但不适用于 c# 语法。这可能就是你想要的。

标签: c# string file text non-ascii-characters


【解决方案1】:

所以,我要做的不是使用这样的纯文本,而是使用适当的序列化格式,例如 JSON。

有一些工具可以为您完成繁重的工作。
内置的 System.Web.Script.Serialization 命名空间有一些工具可以使用,但我更喜欢使用 Json.Net。如果您有 Visual Studio,则可以使用 nuGet 安装它(如果您需要更多帮助,请在 cmets 中告诉我)。

但是一旦你把它添加到你的项目中,你就可以做这样的事情

using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;

namespace ConsoleApplication1
{
    public class Program
    {
        static void Main(string[] args)
        {
            var dict = new Dictionary<string, string>();

            dict.Add("FirstName", "Elmer");
            dict.Add("LastName", "Fudd");
            dict.Add("Password", @"\a\ansld\sb\b8d95nj");

            var json = JsonConvert.SerializeObject(dict);

            File.WriteAllText("ConfigFile.txt, json);

            var txt = File.ReadAllText("ConfigFile.txt");
            var newDict = JsonConvert.DeserializeObject<Dictionary<string, string>>(txt);

        }
    }
}

ConfigFile.txt 看起来像这样

{"FirstName":"Elmer","LastName":"Fudd","Password":"\\a\\ansld\\sb\\b8d95nj"}

如果您希望它更易于阅读,请使用

var json = JsonConvert.SerializeObject(dict, Formatting.Indented);

你会得到

{
  "FirstName": "Elmer",
  "LastName": "Fudd",
  "Password": "\\a\\ansld\\sb\\b8d95nj"
}

【讨论】:

  • 这是您在“密码”之后编码的内容:这就是迫使我这样做的原因。我无法控制加密密码中的字符,也无法编辑它。不能使用转义字符。事实上,加密例程会将一些字符转换为反斜杠,并且必须能够再次将它们转换回来。密码必须与加密机中的密码完全相同,否则无法正确解密。 U R 输出带有转义的加密密码 - 它不会解密。我无法控制加密密码中出现的字符。
  • @MiddleAgedMutantNinjaProgrammer 序列化时字符会被转义,反序列化时会正常。如果你不能在应用中反序列化,那么它完全不适合存储密码。
  • @MiddleAgedMutantNinjaProgrammer 您不需要对加密密码中的字符进行任何控制。只需依靠序列化框架为您解决。
  • 感谢大家的努力,但每个人都想用手提钻敲碎核桃。负责编辑配置文件的用户是移植外科医生及其团队。他们对当前的简单格式感到满意,不会接受,也无法理解任何复杂情况。只是密码现在将使用 0x20 和 0x7e 之间的任何一个字符作为合法的加密替换字符进行加密,这会杀死我在这里看到的任何解决方案。我会自己尝试并解决它。蔡欧。
  • @MiddleAgedMutantNinjaProgrammer 并使用0x200x73 来表示字符对您来说似乎不是一个额外的复杂问题?
【解决方案2】:

您可以将整数转换为字符,所以只需这样做......

string[] tokens = fileText.Split((char)0x1e);
// ...
string[] keyValuePair = token.Split((char)0x1f);

...但是将您的密码编码为 base64 会更容易和更清晰...

string base64 = Convert.ToBase64String(passwordHash);
byte[] passwordHash = Convert.FromBase64String(base64);

... 注意: 哈希/加密数据可能包含这些字符,所以我不会只是将哈希转储到文本文件中。

【讨论】:

    【解决方案3】:

    以下类使用正则表达式提取字符串段并支持不可打印字符的密码:0x00 .. 0xFF 该类包括配置段的属性

    您可以在.NEt Fiddle 运行演示示例

    using System;
    using System.Text.RegularExpressions;
    
    
    class ConfigParser
    {
        public string Text { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UserId { get; set; }
        public string Password { get; set; }
        public string Date { get; set; }
    
        public ConfigParser(string text)
        {
            Text =text;
            Parse(text);
        }
    
    
        private static string pattern = @"
         ^FirstName=(?<firstname>\w+)    \|          
         LastName=(?<lastname>\w+)       \|              
         UserId=(?<userid>\w+)           \|                  
         Password=(?<pasword>.+)        
         Date=(?<date>.+)                         
         $
        ";
    
        private Regex regex = new Regex(pattern,
               RegexOptions.Singleline
               | RegexOptions.ExplicitCapture
               | RegexOptions.CultureInvariant
               | RegexOptions.IgnorePatternWhitespace
               | RegexOptions.Compiled
               );
    
    
    
        private void Parse(string text)
        {
            Console.WriteLine("text: {0}",text);
            Match m = regex.Match(text);
            FirstName = m.Groups["firstname"].ToString();
            LastName = m.Groups["lastname"].ToString();
            UserId = m.Groups["userid"].ToString();
            Password = m.Groups["pasword"].ToString();
            Date = m.Groups["date"].ToString();
    
        }
    
    }
    

    使用方法:

       var text ="your text here"; 
       var c = new ConfigParser(text );             
    
       you can access the properties of the class: FirstName, LastName,....
    
       Console.WriteLine("firstname: {0}", c.FirstName);
       Console.WriteLine("lastname: {0}", c.LastName);
       Console.WriteLine("UserId: {0}", c.UserId);
       Console.WriteLine("Password: {0}", c.Password);
       Console.WriteLine("date {0}", c.Date);
    

    示例输出: 密码包含不可打印的字符 |分隔符和符号

    text: FirstName=Elmer|LastName=Fudd|UserId=EFudd|Password=fg%|uy|◄¶|hj↑khg|Date=7/29/2016
    firstname: Elmer
    lastname: Fudd
    UserId: EFudd
    Password: fg%|uy|◄¶|hj↑khg
    date: 7/29/2016
    

    【讨论】:

      【解决方案4】:

      最简单的答案:

      使用 ALT 数字键盘值技巧将特殊字符插入到字符串中。记录组 ALT-31 (▼) 以分隔键/值对的结尾和项目组 ALT-30 (▲) 以分隔键与值。将字符串保存为 UTF-8。

      分隔符的代码是

      private static char tokenDelimiter = ('▲');
      private static char keyValuePairDelimiter = ('▼');
      

      使用相同的 ALT 数字键盘技巧来输入上下三角形。包括说明不得编辑或删除黑色三角形并解释其含义。

      这让我回到了我以前的 DOS 时代。很简单,实施只需要 5 分钟 - 而且不需要对现有代码库进行实质性更改 - 只需更改两个分隔符即可。

      【讨论】:

      • 感谢所有想出各种技术来做到这一点的人。如果这是一个新建项目,我会做很多不同的事情。但是,我在一个 IT 部门工作,它仍然支持从 90 年代的 ASP(前 ASP.NET)和 Access 数据库,甚至是 IBM 360/370 大型机 BAL(基本汇编语言)。当我有空做自己的项目时,我通常会使用学术界刚刚开发的技术。在 SOAP/XML 出现之前,我一直在使用 REST/JSON。这个应用程序是一个杂物,但我不能更改它,我不拥有它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-08
      • 1970-01-01
      • 1970-01-01
      • 2017-10-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多