1 /*
2  * PROJECT:     ReactOS Applications Manager
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Config parser
5  * COPYRIGHT:   Copyright 2009 Dmitry Chapyshev (dmitry@reactos.org)
6  *              Copyright 2015 Ismael Ferreras Morezuelas (swyterzone+ros@gmail.com)
7  *              Copyright 2017 Alexander Shaposhnikov (sanchaez@reactos.org)
8  *              Copyright 2021 Mark Jansen <mark.jansen@reactos.org>
9  */
10 
11 #include "rapps.h"
12 #include <debug.h>
13 
14 struct CLocaleSections
15 {
16     CStringW Locale;
17     CStringW LocaleNeutral;
18     CStringW Section;
19 };
20 
21 struct CSectionNames
22 {
23     CLocaleSections ArchSpecific;
24     CLocaleSections ArchNeutral;
25 };
26 static CSectionNames g_Names;
27 
28 HRESULT
ReadIniValue(LPCWSTR File,LPCWSTR Section,LPCWSTR Name,CStringW & Output)29 ReadIniValue(LPCWSTR File, LPCWSTR Section, LPCWSTR Name, CStringW &Output)
30 {
31     for (DWORD len = 256, ret;; len *= 2)
32     {
33         ret = GetPrivateProfileString(Section, Name, L"\n", Output.GetBuffer(len), len, File);
34         if (ret + 1 != len)
35         {
36             Output.ReleaseBuffer(ret);
37             return ret && Output[0] != L'\n' ? ret : HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
38         }
39     }
40 }
41 
CConfigParser(const CStringW & FilePath)42 CConfigParser::CConfigParser(const CStringW &FilePath) : szConfigPath(FilePath)
43 {
44     CacheINI();
45 }
46 
47 void
ReadSection(CStringW & Buffer,const CStringW & Section,BOOL isArch)48 CConfigParser::ReadSection(CStringW &Buffer, const CStringW &Section, BOOL isArch)
49 {
50     DWORD len = 512;
51     DWORD result;
52 
53     do
54     {
55         len *= 2;
56 
57         result = GetPrivateProfileSectionW(Section, Buffer.GetBuffer(len), len, szConfigPath);
58         Buffer.ReleaseBuffer(result);
59     } while (result == len - 2);
60 
61     len = 0;
62     while (len < result)
63     {
64         // Explicitly use the null terminator!
65         CString tmp = Buffer.GetBuffer() + len;
66         if (tmp.GetLength() > 0)
67         {
68             len += tmp.GetLength() + 1;
69 
70             int idx = tmp.Find('=');
71             if (idx >= 0)
72             {
73                 CString key = tmp.Left(idx);
74 
75 #ifndef _M_IX86
76                 // On non-x86 architecture we need the architecture specific URL
77                 if (!isArch && key == "URLDownload")
78                 {
79                     continue;
80                 }
81 #endif
82 
83                 // Is this key already present from a more specific translation?
84                 if (m_Keys.FindKey(key) >= 0)
85                 {
86                     continue;
87                 }
88 
89                 CString value = tmp.Mid(idx + 1);
90                 m_Keys.Add(key, value);
91             }
92             else
93             {
94                 DPRINT1("ERROR: invalid key/value pair: '%S'\n", tmp.GetString());
95             }
96         }
97         else
98         {
99             break;
100         }
101     }
102 }
103 
104 VOID
CacheINI()105 CConfigParser::CacheINI()
106 {
107     // Cache section names
108     if (g_Names.ArchSpecific.Locale.IsEmpty())
109     {
110         CString szLocaleID;
111         const INT cchLocaleSize = 5;
112 
113         GetLocaleInfoW(GetUserDefaultLCID(), LOCALE_ILANGUAGE, szLocaleID.GetBuffer(cchLocaleSize), cchLocaleSize);
114         szLocaleID.ReleaseBuffer();
115         CString INISectionLocale = L"Section." + szLocaleID;
116 
117         g_Names.ArchSpecific.Locale = INISectionLocale + L"." CurrentArchitecture;
118         g_Names.ArchNeutral.Locale = INISectionLocale;
119 
120         // turn "Section.0c0a" into "Section.0a", keeping just the neutral lang part
121         if (szLocaleID.GetLength() >= 2)
122         {
123             g_Names.ArchSpecific.LocaleNeutral = L"Section." + szLocaleID.Right(2) + L"." CurrentArchitecture;
124             g_Names.ArchNeutral.LocaleNeutral = L"Section." + szLocaleID.Right(2);
125         }
126 
127         g_Names.ArchSpecific.Section = L"Section." CurrentArchitecture;
128         g_Names.ArchNeutral.Section = L"Section";
129     }
130 
131     // Use a shared buffer so that we don't have to re-allocate it every time
132     CStringW Buffer;
133 
134     ReadSection(Buffer, g_Names.ArchSpecific.Locale, TRUE);
135     if (!g_Names.ArchSpecific.LocaleNeutral.IsEmpty())
136     {
137         ReadSection(Buffer, g_Names.ArchSpecific.LocaleNeutral, TRUE);
138     }
139     ReadSection(Buffer, g_Names.ArchSpecific.Section, TRUE);
140 
141     ReadSection(Buffer, g_Names.ArchNeutral.Locale, FALSE);
142     if (!g_Names.ArchNeutral.LocaleNeutral.IsEmpty())
143     {
144         ReadSection(Buffer, g_Names.ArchNeutral.LocaleNeutral, FALSE);
145     }
146     ReadSection(Buffer, g_Names.ArchNeutral.Section, FALSE);
147 }
148 
149 BOOL
GetString(const CStringW & KeyName,CStringW & ResultString)150 CConfigParser::GetString(const CStringW &KeyName, CStringW &ResultString)
151 {
152     int nIndex = m_Keys.FindKey(KeyName);
153     if (nIndex >= 0)
154     {
155         ResultString = m_Keys.GetValueAt(nIndex);
156         return TRUE;
157     }
158 
159     ResultString.Empty();
160     return FALSE;
161 }
162 
163 BOOL
GetInt(const CStringW & KeyName,INT & iResult)164 CConfigParser::GetInt(const CStringW &KeyName, INT &iResult)
165 {
166     CStringW Buffer;
167 
168     iResult = 0;
169 
170     // grab the text version of our entry
171     if (!GetString(KeyName, Buffer))
172         return FALSE;
173 
174     if (Buffer.IsEmpty())
175         return FALSE;
176 
177     // convert it to an actual integer
178     iResult = StrToIntW(Buffer.GetString());
179 
180     // we only care about values > 0
181     return (iResult > 0);
182 }
183 
184 UINT
GetSectionString(LPCWSTR Section,LPCWSTR Name,CStringW & Result)185 CConfigParser::GetSectionString(LPCWSTR Section, LPCWSTR Name, CStringW &Result)
186 {
187     HRESULT hr; // Return value; length of ini string or 0 on failure.
188     CStringW SecBuf;
189     WCHAR FullLoc[5], *NeutralLoc = FullLoc + 2;
190     GetLocaleInfoW(GetUserDefaultLCID(), LOCALE_ILANGUAGE, FullLoc, _countof(FullLoc));
191 
192     SecBuf.Format(L"%s.%s.%s", Section, FullLoc, CurrentArchitecture);
193     if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0)
194         return hr;
195 
196     if (*NeutralLoc)
197     {
198         SecBuf.Format(L"%s.%s.%s", Section, NeutralLoc, CurrentArchitecture);
199         if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0)
200             return hr;
201     }
202 
203     SecBuf.Format(L"%s.%s", Section, CurrentArchitecture);
204     if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0)
205         return hr;
206 
207     SecBuf.Format(L"%s.%s", Section, FullLoc);
208     if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0)
209         return hr;
210 
211     if (*NeutralLoc)
212     {
213         SecBuf.Format(L"%s.%s", Section, NeutralLoc);
214         if ((hr = ReadIniValue(szConfigPath, SecBuf, Name, Result)) > 0)
215             return hr;
216     }
217 
218     if ((hr = ReadIniValue(szConfigPath, Section, Name, Result)) > 0)
219         return hr;
220     return 0;
221 }
222