@@ -30,6 +30,19 @@ public sealed partial class IniFile
3030 /// <summary>The fully-qualified path to the INI file.</summary>
3131 private readonly string _filePath ;
3232
33+ private static string NormalizeSection ( string ? section ) => section ?? string . Empty ;
34+
35+ private static void ValidateBufferSize ( int bufferSize , int minimumSize = 2 )
36+ {
37+ if ( bufferSize < minimumSize || bufferSize > MaxSectionBufferSize )
38+ {
39+ throw new ArgumentOutOfRangeException (
40+ nameof ( bufferSize ) ,
41+ bufferSize ,
42+ $ "Buffer size must be between { minimumSize } and { MaxSectionBufferSize } characters.") ;
43+ }
44+ }
45+
3346 /// <summary>
3447 /// Initializes a new instance of the <see cref="IniFile"/> class
3548 /// bound to the specified file path.
@@ -83,7 +96,7 @@ private static partial int NativeGetPrivateProfileString(
8396
8497 /// <summary>
8598 /// Retrieves an integer value from an INI file.
86- /// If the key is not found or the value is not a valid integer , <paramref name="nDefault"/> is returned.
99+ /// If the key is not found, <paramref name="nDefault"/> is returned.
87100 /// </summary>
88101 [ LibraryImport ( "kernel32" , EntryPoint = "GetPrivateProfileIntW" ,
89102 StringMarshalling = StringMarshalling . Utf16 ) ]
@@ -130,47 +143,67 @@ private static partial int NativeGetPrivateProfileSectionNames(
130143 /// <param name="key">The key name.</param>
131144 /// <param name="value">The string value to write.</param>
132145 /// <param name="section">
133- /// The section name. Pass <c>null</c> to target the unnamed (global) area of the file .
146+ /// The section name. Pass <c>null</c> to target the empty section name, which Windows serializes as <c>[]</c> .
134147 /// </param>
135148 /// <returns><c>true</c> if the write succeeded; otherwise <c>false</c>.</returns>
149+ /// <exception cref="ArgumentException">
150+ /// Thrown when <paramref name="key"/> is <c>null</c>, empty, or whitespace.
151+ /// </exception>
136152 public bool Write ( string key , string ? value , string ? section = null )
137- => NativeWritePrivateProfileString ( section , key , value , _filePath ) ;
153+ {
154+ ArgumentException . ThrowIfNullOrWhiteSpace ( key ) ;
155+ return NativeWritePrivateProfileString ( NormalizeSection ( section ) , key , value , _filePath ) ;
156+ }
138157
139158 /// <summary>
140159 /// Reads a string value from the specified key in the given section of the INI file.
141160 /// </summary>
142161 /// <param name="key">
143162 /// The key name. If <c>null</c>, all key names in <paramref name="section"/> are returned
144- /// as a single null-separated string.
163+ /// as a single null-separated string. If both <paramref name="key"/> and
164+ /// <paramref name="section"/> are <c>null</c>, all section names are returned.
145165 /// </param>
146166 /// <param name="section">
147- /// The section name. If <c>null</c>, all section names in the file are returned.
167+ /// The section name. When <paramref name="key"/> is not <c>null</c>, pass <c>null</c>
168+ /// to target the empty section name, which Windows serializes as <c>[]</c>.
148169 /// </param>
149170 /// <param name="defaultValue">
150171 /// The value returned when the key is not found. Defaults to an empty string.
151172 /// </param>
152173 /// <param name="bufferSize">
153174 /// The initial size of the internal read buffer in characters. Defaults to <see cref="DefaultBufferSize"/>.
154175 /// The buffer doubles automatically if the value exceeds it, up to <see cref="MaxSectionBufferSize"/>.
176+ /// For native key/section enumeration, the buffer must be at least 3 characters.
155177 /// </param>
156178 /// <returns>
157179 /// The value associated with the key, or <paramref name="defaultValue"/> if the key was not found.
158180 /// </returns>
159- public string ReadString ( string key , string ? section = null ,
181+ public string ReadString ( string ? key , string ? section = null ,
160182 string defaultValue = "" , int bufferSize = DefaultBufferSize )
161183 {
184+ ValidateBufferSize ( bufferSize , key is null ? 3 : 2 ) ;
185+
186+ string ? nativeSection = key is null ? section : NormalizeSection ( section ) ;
187+
162188 while ( true )
163189 {
164190 char [ ] buffer = new char [ bufferSize ] ;
165- int length = NativeGetPrivateProfileString ( section , key , defaultValue , buffer , bufferSize , _filePath ) ;
191+ int length = NativeGetPrivateProfileString ( nativeSection , key , defaultValue , buffer , bufferSize , _filePath ) ;
192+ int truncationLength = key is null ? bufferSize - 2 : bufferSize - 1 ;
166193
167- // If length == bufferSize - 1, the buffer was too small and the value was truncated .
168- if ( length == bufferSize - 1 && bufferSize < MaxSectionBufferSize )
194+ // String reads signal truncation at nSize - 1; enumeration reads signal it at nSize - 2 .
195+ if ( length >= truncationLength && bufferSize < MaxSectionBufferSize )
169196 {
170197 bufferSize = Math . Min ( bufferSize * 2 , MaxSectionBufferSize ) ;
171198 continue ;
172199 }
173200
201+ if ( key is null && length >= truncationLength )
202+ {
203+ throw new InvalidOperationException (
204+ $ "INI data exceeds the maximum buffer size of { MaxSectionBufferSize } characters.") ;
205+ }
206+
174207 return new string ( buffer , 0 , length ) ;
175208 }
176209 }
@@ -179,24 +212,29 @@ public string ReadString(string key, string? section = null,
179212 /// Reads an integer value from the specified key in the given section of the INI file.
180213 /// </summary>
181214 /// <param name="key">The key name.</param>
182- /// <param name="section">The section name.</param>
215+ /// <param name="section">The section name, or <c>null</c> for the empty section name serialized as <c>[]</c> .</param>
183216 /// <param name="defaultValue">
184- /// The value returned when the key is not found or its value is not a valid integer.
185- /// Defaults to <c>-1</c>.
217+ /// The value returned when the key is not found. Defaults to <c>-1</c>.
186218 /// </param>
187219 /// <returns>
188- /// The integer value of the key, or <paramref name="defaultValue"/> if the key was not found
189- /// or could not be parsed. If the stored value starts with digits followed by non-digit
190- /// characters (e.g. <c>"10apples" </c>), only the leading numeric portion is returned (<c>10</c>) .
220+ /// The integer value of the key, or <paramref name="defaultValue"/> if the key was not found.
221+ /// Malformed numeric values follow the native <c>GetPrivateProfileInt</c> parsing behavior
222+ /// and may return <c>0 </c> instead of <paramref name="defaultValue"/> .
191223 /// </returns>
224+ /// <exception cref="ArgumentException">
225+ /// Thrown when <paramref name="key"/> is <c>null</c>, empty, or whitespace.
226+ /// </exception>
192227 public int ReadInt ( string key , string ? section = null , int defaultValue = - 1 )
193- => NativeGetPrivateProfileInt ( section , key , defaultValue , _filePath ) ;
228+ {
229+ ArgumentException . ThrowIfNullOrWhiteSpace ( key ) ;
230+ return NativeGetPrivateProfileInt ( NormalizeSection ( section ) , key , defaultValue , _filePath ) ;
231+ }
194232
195233 /// <summary>
196234 /// Reads a boolean value from the specified key in the given section of the INI file.
197235 /// </summary>
198236 /// <param name="key">The key name.</param>
199- /// <param name="section">The section name.</param>
237+ /// <param name="section">The section name, or <c>null</c> for the empty section name serialized as <c>[]</c> .</param>
200238 /// <param name="defaultValue">
201239 /// The value returned when the key is not found or cannot be parsed. Defaults to <c>false</c>.
202240 /// </param>
@@ -207,6 +245,7 @@ public int ReadInt(string key, string? section = null, int defaultValue = -1)
207245 /// </returns>
208246 public bool ReadBool ( string key , string ? section = null , bool defaultValue = false )
209247 {
248+ ArgumentException . ThrowIfNullOrWhiteSpace ( key ) ;
210249 string value = ReadString ( key , section ) ;
211250
212251 if ( string . IsNullOrWhiteSpace ( value ) )
@@ -242,6 +281,7 @@ public bool ReadBool(string key, string? section = null, bool defaultValue = fal
242281 public string [ ] GetAllDataSection ( string section , int bufferSize = MaxSectionBufferSize )
243282 {
244283 ArgumentException . ThrowIfNullOrWhiteSpace ( section ) ;
284+ ValidateBufferSize ( bufferSize ) ;
245285
246286 nint pMem = Marshal . AllocHGlobal ( bufferSize * sizeof ( char ) ) ;
247287 try
@@ -283,6 +323,8 @@ public string[] GetAllDataSection(string section, int bufferSize = MaxSectionBuf
283323 /// </exception>
284324 public string [ ] GetAllSections ( int bufferSize = MaxSectionBufferSize )
285325 {
326+ ValidateBufferSize ( bufferSize ) ;
327+
286328 nint pMem = Marshal . AllocHGlobal ( bufferSize * sizeof ( char ) ) ;
287329 try
288330 {
@@ -313,38 +355,40 @@ public string[] GetAllSections(int bufferSize = MaxSectionBufferSize)
313355 /// Deletes the specified key (and its value) from the given section of the INI file.
314356 /// </summary>
315357 /// <param name="key">The key name to delete.</param>
316- /// <param name="section">The section containing the key.</param>
358+ /// <param name="section">The section containing the key, or <c>null</c> for the empty section name serialized as <c>[]</c> .</param>
317359 /// <returns><c>true</c> if the operation succeeded; otherwise <c>false</c>.</returns>
318360 /// <exception cref="ArgumentException">
319361 /// Thrown when <paramref name="key"/> is <c>null</c>, empty, or whitespace.
320362 /// </exception>
321363 public bool DeleteKey ( string key , string ? section = null )
322364 {
323365 ArgumentException . ThrowIfNullOrWhiteSpace ( key ) ;
324- return NativeWritePrivateProfileString ( section , key , null , _filePath ) ;
366+ return NativeWritePrivateProfileString ( NormalizeSection ( section ) , key , null , _filePath ) ;
325367 }
326368
327369 /// <summary>
328370 /// Deletes the specified section and all of its keys from the INI file.
329371 /// </summary>
330- /// <param name="section">The section name to delete.</param>
372+ /// <param name="section">The section name to delete, or <c>null</c> for the empty section name serialized as <c>[]</c> .</param>
331373 /// <returns><c>true</c> if the operation succeeded; otherwise <c>false</c>.</returns>
332374 public bool DeleteSection ( string ? section = null )
333- => NativeWritePrivateProfileString ( section , null , null , _filePath ) ;
375+ => NativeWritePrivateProfileString ( NormalizeSection ( section ) , null , null , _filePath ) ;
334376
335377 /// <summary>
336378 /// Checks whether the specified key exists in the given section of the INI file.
337379 /// </summary>
338380 /// <param name="key">The key name to check.</param>
339- /// <param name="section">The section containing the key.</param>
381+ /// <param name="section">The section containing the key, or <c>null</c> for the empty section name serialized as <c>[]</c> .</param>
340382 /// <returns>
341383 /// <c>true</c> if the key exists (even if its value is empty); otherwise <c>false</c>.
342384 /// </returns>
343385 public bool KeyExists ( string key , string ? section = null )
344386 {
387+ ArgumentException . ThrowIfNullOrWhiteSpace ( key ) ;
388+
345389 char [ ] buffer = new char [ MaxSectionBufferSize ] ;
346390 // Passing null for lpKeyName returns all key names in the section separated by \0.
347- int length = NativeGetPrivateProfileString ( section , null , null , buffer , MaxSectionBufferSize , _filePath ) ;
391+ int length = NativeGetPrivateProfileString ( NormalizeSection ( section ) , null , null , buffer , MaxSectionBufferSize , _filePath ) ;
348392 if ( length <= 0 ) return false ;
349393
350394 string raw = new string ( buffer , 0 , length ) ;
0 commit comments