regedit: Fix concat handling in unicode import.
[wine] / programs / regedit / edit.c
1 /*
2  * Registry editing UI functions.
3  *
4  * Copyright (C) 2003 Dimitrie O. Paun
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #define WIN32_LEAN_AND_MEAN     /* Exclude rarely-used stuff from Windows headers */
22
23 #include <windows.h>
24 #include <tchar.h>
25 #include <commctrl.h>
26 #include <commdlg.h>
27 #include <cderr.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <shellapi.h>
31 #include <shlwapi.h>
32
33 #include "main.h"
34 #include "regproc.h"
35 #include "resource.h"
36
37 static const TCHAR* editValueName;
38 static TCHAR* stringValueData;
39 static BOOL isDecimal;
40
41 struct edit_params
42 {
43     HKEY    hKey;
44     LPCTSTR lpszValueName;
45     void   *pData;
46     LONG    cbData;
47 };
48
49 static INT vmessagebox(HWND hwnd, INT buttons, INT titleId, INT resId, va_list ap)
50 {
51     TCHAR title[256];
52     TCHAR errfmt[1024];
53     TCHAR errstr[1024];
54
55     if (!LoadString(hInst, titleId, title, COUNT_OF(title)))
56         lstrcpy(title, "Error");
57
58     if (!LoadString(hInst, resId, errfmt, COUNT_OF(errfmt)))
59         lstrcpy(errfmt, "Unknown error string!");
60
61     _vsntprintf(errstr, COUNT_OF(errstr), errfmt, ap);
62
63     return MessageBox(hwnd, errstr, title, buttons);
64 }
65
66 static INT messagebox(HWND hwnd, INT buttons, INT titleId, INT resId, ...)
67 {
68     va_list ap;
69     INT result;
70
71     va_start(ap, resId);
72     result = vmessagebox(hwnd, buttons, titleId, resId, ap);
73     va_end(ap);
74
75     return result;
76 }
77
78 void error(HWND hwnd, INT resId, ...)
79 {
80     va_list ap;
81
82     va_start(ap, resId);
83     vmessagebox(hwnd, MB_OK | MB_ICONERROR, IDS_ERROR, resId, ap);
84     va_end(ap);
85 }
86
87 static void error_code_messagebox(HWND hwnd, DWORD error_code)
88 {
89     LPTSTR lpMsgBuf;
90     DWORD status;
91     TCHAR title[256];
92     static TCHAR fallback[] = TEXT("Error displaying error message.\n");
93     if (!LoadString(hInst, IDS_ERROR, title, COUNT_OF(title)))
94         lstrcpy(title, TEXT("Error"));
95     status = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
96                            NULL, error_code, 0, (LPTSTR)&lpMsgBuf, 0, NULL);
97     if (!status)
98         lpMsgBuf = fallback;
99     MessageBox(hwnd, lpMsgBuf, title, MB_OK | MB_ICONERROR);
100     if (lpMsgBuf != fallback)
101         LocalFree(lpMsgBuf);
102 }
103
104 static BOOL change_dword_base(HWND hwndDlg, BOOL toHex)
105 {
106     TCHAR buf[128];
107     DWORD val;
108
109     if (!GetDlgItemText(hwndDlg, IDC_VALUE_DATA, buf, COUNT_OF(buf))) return FALSE;
110     if (!_stscanf(buf, toHex ? "%u" : "%x", &val)) return FALSE;
111     wsprintf(buf, toHex ? "%x" : "%u", val);
112     return SetDlgItemText(hwndDlg, IDC_VALUE_DATA, buf);    
113 }
114
115 static INT_PTR CALLBACK modify_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
116 {
117     TCHAR* valueData;
118     HWND hwndValue;
119     int len;
120
121     switch(uMsg) {
122     case WM_INITDIALOG:
123         SetDlgItemText(hwndDlg, IDC_VALUE_NAME, editValueName);
124         SetDlgItemText(hwndDlg, IDC_VALUE_DATA, stringValueData);
125         CheckRadioButton(hwndDlg, IDC_DWORD_HEX, IDC_DWORD_DEC, isDecimal ? IDC_DWORD_DEC : IDC_DWORD_HEX);
126         return TRUE;
127     case WM_COMMAND:
128         switch (LOWORD(wParam)) {
129         case IDC_DWORD_HEX:
130             if (isDecimal && change_dword_base(hwndDlg, TRUE)) isDecimal = FALSE;
131         break;
132         case IDC_DWORD_DEC:
133             if (!isDecimal && change_dword_base(hwndDlg, FALSE)) isDecimal = TRUE;
134         break;
135         case IDOK:
136             if ((hwndValue = GetDlgItem(hwndDlg, IDC_VALUE_DATA))) {
137                 len = GetWindowTextLength(hwndValue);
138                 if ((valueData = HeapReAlloc(GetProcessHeap(), 0, stringValueData, (len + 1) * sizeof(TCHAR)))) {
139                     stringValueData = valueData;
140                     if (!GetWindowText(hwndValue, stringValueData, len + 1))
141                         *stringValueData = 0;
142                 }
143             }
144             /* Fall through */
145         case IDCANCEL:
146             EndDialog(hwndDlg, wParam);
147             return TRUE;
148         }
149     }
150     return FALSE;
151 }
152
153 static INT_PTR CALLBACK bin_modify_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
154 {
155     struct edit_params *params;
156     LPBYTE pData;
157     LONG cbData;
158     LONG lRet;
159
160     switch(uMsg) {
161     case WM_INITDIALOG:
162         params = (struct edit_params *)lParam;
163         SetWindowLongPtr(hwndDlg, DWLP_USER, (ULONG_PTR)params);
164         if (params->lpszValueName)
165             SetDlgItemText(hwndDlg, IDC_VALUE_NAME, params->lpszValueName);
166         else
167             SetDlgItemText(hwndDlg, IDC_VALUE_NAME, g_pszDefaultValueName);
168         SendDlgItemMessage(hwndDlg, IDC_VALUE_DATA, HEM_SETDATA, (WPARAM)params->cbData, (LPARAM)params->pData);
169         return TRUE;
170     case WM_COMMAND:
171         switch (LOWORD(wParam)) {
172         case IDOK:
173             params = (struct edit_params *)GetWindowLongPtr(hwndDlg, DWLP_USER);
174             cbData = SendDlgItemMessage(hwndDlg, IDC_VALUE_DATA, HEM_GETDATA, 0, 0);
175             pData = HeapAlloc(GetProcessHeap(), 0, cbData);
176
177             if (pData)
178             {
179                 SendDlgItemMessage(hwndDlg, IDC_VALUE_DATA, HEM_GETDATA, (WPARAM)cbData, (LPARAM)pData);
180                 lRet = RegSetValueEx(params->hKey, params->lpszValueName, 0, REG_BINARY, pData, cbData);
181             }
182             else
183                 lRet = ERROR_OUTOFMEMORY;
184
185             if (lRet == ERROR_SUCCESS)
186                 EndDialog(hwndDlg, 1);
187             else
188             {
189                 error_code_messagebox(hwndDlg, lRet);
190                 EndDialog(hwndDlg, 0);
191             }
192             return TRUE;
193         case IDCANCEL:
194             EndDialog(hwndDlg, 0);
195             return TRUE;
196         }
197     }
198     return FALSE;
199 }
200
201 static BOOL check_value(HWND hwnd, HKEY hKey, LPCTSTR valueName)
202 {
203     LONG lRet = RegQueryValueEx(hKey, valueName ? valueName : _T(""), 0, NULL, 0, NULL);
204     if(lRet != ERROR_SUCCESS) return FALSE;
205     return TRUE;
206 }
207
208 static LPTSTR read_value(HWND hwnd, HKEY hKey, LPCTSTR valueName, DWORD *lpType, LONG *len)
209 {
210     DWORD valueDataLen;
211     LPTSTR buffer = NULL;
212     LONG lRet;
213
214     lRet = RegQueryValueEx(hKey, valueName ? valueName : _T(""), 0, lpType, 0, &valueDataLen);
215     if (lRet != ERROR_SUCCESS) {
216         if (lRet == ERROR_FILE_NOT_FOUND && !valueName) { /* no default value here, make it up */
217             if (len) *len = 1;
218             if (lpType) *lpType = REG_SZ;
219             buffer = HeapAlloc(GetProcessHeap(), 0, 1);
220             *buffer = '\0';
221             return buffer;
222         }
223         error(hwnd, IDS_BAD_VALUE, valueName);
224         goto done;
225     }
226     if ( *lpType == REG_DWORD ) valueDataLen = sizeof(DWORD);
227     if (!(buffer = HeapAlloc(GetProcessHeap(), 0, valueDataLen+1))) {
228         error(hwnd, IDS_TOO_BIG_VALUE, valueDataLen);
229         goto done;
230     }
231     lRet = RegQueryValueEx(hKey, valueName, 0, 0, (LPBYTE)buffer, &valueDataLen);
232     if (lRet != ERROR_SUCCESS) {
233         error(hwnd, IDS_BAD_VALUE, valueName);
234         goto done;
235     }
236     buffer[valueDataLen] = 0;
237     if(len) *len = valueDataLen;
238     return buffer;
239
240 done:
241     HeapFree(GetProcessHeap(), 0, buffer);
242     return NULL;
243 }
244
245 BOOL CreateKey(HWND hwnd, HKEY hKeyRoot, LPCTSTR keyPath, LPTSTR keyName)
246 {
247     BOOL result = FALSE;
248     LONG lRet = ERROR_SUCCESS;
249     HKEY retKey = NULL;
250     TCHAR newKey[MAX_NEW_KEY_LEN - 4];
251     int keyNum;
252     HKEY hKey;
253          
254     lRet = RegOpenKeyEx(hKeyRoot, keyPath, 0, KEY_CREATE_SUB_KEY, &hKey);
255     if (lRet != ERROR_SUCCESS) {
256         error_code_messagebox(hwnd, lRet);
257         goto done;
258     }
259
260     if (!LoadString(GetModuleHandle(0), IDS_NEWKEY, newKey, COUNT_OF(newKey))) goto done;
261
262     /* try to find out a name for the newly create key (max 100 times) */
263     for (keyNum = 1; keyNum < 100; keyNum++) {
264         wsprintf(keyName, newKey, keyNum);
265         lRet = RegOpenKey(hKey, keyName, &retKey);
266         if (lRet != ERROR_SUCCESS) break;
267         RegCloseKey(retKey);
268     }
269     if (lRet == ERROR_SUCCESS) goto done;
270     
271     lRet = RegCreateKey(hKey, keyName, &retKey);
272     if (lRet != ERROR_SUCCESS) {
273         error_code_messagebox(hwnd, lRet);
274         goto done;
275     }
276
277     result = TRUE;
278
279 done:
280     RegCloseKey(retKey);
281     return result;
282 }
283
284 BOOL ModifyValue(HWND hwnd, HKEY hKeyRoot, LPCTSTR keyPath, LPCTSTR valueName)
285 {
286     BOOL result = FALSE;
287     DWORD type;
288     LONG lRet;
289     HKEY hKey;
290     LONG len;
291
292     lRet = RegOpenKeyEx(hKeyRoot, keyPath, 0, KEY_READ | KEY_SET_VALUE, &hKey);
293     if (lRet != ERROR_SUCCESS) {
294         error_code_messagebox(hwnd, lRet);
295         return FALSE;
296     }
297
298     editValueName = valueName ? valueName : g_pszDefaultValueName;
299     if(!(stringValueData = read_value(hwnd, hKey, valueName, &type, &len))) goto done;
300
301     if ( (type == REG_SZ) || (type == REG_EXPAND_SZ) ) {
302         if (DialogBox(0, MAKEINTRESOURCE(IDD_EDIT_STRING), hwnd, modify_dlgproc) == IDOK) {
303             lRet = RegSetValueEx(hKey, valueName, 0, type, (LPBYTE)stringValueData, lstrlen(stringValueData) + 1);
304             if (lRet == ERROR_SUCCESS) result = TRUE;
305             else error_code_messagebox(hwnd, lRet);
306         }
307     } else if ( type == REG_DWORD ) {
308         wsprintf(stringValueData, isDecimal ? "%u" : "%x", *((DWORD*)stringValueData));
309         if (DialogBox(0, MAKEINTRESOURCE(IDD_EDIT_DWORD), hwnd, modify_dlgproc) == IDOK) {
310             DWORD val;
311             if (_stscanf(stringValueData, isDecimal ? "%u" : "%x", &val)) {
312                 lRet = RegSetValueEx(hKey, valueName, 0, type, (BYTE*)&val, sizeof(val));
313                 if (lRet == ERROR_SUCCESS) result = TRUE;
314                 else error_code_messagebox(hwnd, lRet);
315             }
316         }
317     } else if ( type == REG_BINARY ) {
318         struct edit_params params;
319         params.hKey = hKey;
320         params.lpszValueName = valueName;
321         params.pData = stringValueData;
322         params.cbData = len;
323         result = DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_EDIT_BINARY), hwnd,
324             bin_modify_dlgproc, (LPARAM)&params);
325     } else if ( type == REG_MULTI_SZ ) {
326         TCHAR char1 = (TCHAR)'\r', char2 = (TCHAR)'\n';
327         TCHAR *tmpValueData = NULL;
328         INT i, j, count;
329
330         for ( i = 0, count = 0; i < len - 1; i++)
331             if ( !stringValueData[i] && stringValueData[i + 1] )
332                 count++;
333         tmpValueData = HeapAlloc( GetProcessHeap(), 0, ( len + count ) * sizeof(TCHAR));
334         if ( !tmpValueData ) goto done;
335
336         for ( i = 0, j = 0; i < len - 1; i++)
337         {
338             if ( !stringValueData[i] && stringValueData[i + 1])
339             {
340                 tmpValueData[j++] = char1;
341                 tmpValueData[j++] = char2;
342             }
343             else
344                 tmpValueData[j++] = stringValueData[i];
345         }
346         tmpValueData[j] = stringValueData[i];
347         HeapFree( GetProcessHeap(), 0, stringValueData);
348         stringValueData = tmpValueData;
349         tmpValueData = NULL;
350
351         if (DialogBox(0, MAKEINTRESOURCE(IDD_EDIT_MULTI_STRING), hwnd, modify_dlgproc) == IDOK)
352         {
353             len = lstrlen( stringValueData );
354             tmpValueData = HeapAlloc( GetProcessHeap(), 0, (len + 2) * sizeof(TCHAR));
355             if ( !tmpValueData ) goto done;
356
357             for ( i = 0, j = 0; i < len - 1; i++)
358             {
359                 if ( stringValueData[i] == char1 && stringValueData[i + 1] == char2)
360                 {
361                     if ( tmpValueData[j - 1] != 0)
362                         tmpValueData[j++] = 0;
363                     i++;
364                 }
365                 else
366                     tmpValueData[j++] = stringValueData[i];
367             }
368             tmpValueData[j++] = stringValueData[i];
369             tmpValueData[j++] = 0;
370             tmpValueData[j++] = 0;
371             HeapFree( GetProcessHeap(), 0, stringValueData);
372             stringValueData = tmpValueData;
373
374             lRet = RegSetValueEx(hKey, valueName, 0, type, (LPBYTE)stringValueData, j * sizeof(TCHAR));
375             if (lRet == ERROR_SUCCESS) result = TRUE;
376             else error_code_messagebox(hwnd, lRet);
377         }
378     } else {
379         error(hwnd, IDS_UNSUPPORTED_TYPE, type);
380     }
381
382 done:
383     HeapFree(GetProcessHeap(), 0, stringValueData);
384     stringValueData = NULL;
385     RegCloseKey(hKey);
386     return result;
387 }
388
389 BOOL DeleteKey(HWND hwnd, HKEY hKeyRoot, LPCTSTR keyPath)
390 {
391     BOOL result = FALSE;
392     LONG lRet;
393     HKEY hKey;
394     
395     lRet = RegOpenKeyEx(hKeyRoot, keyPath, 0, KEY_READ|KEY_SET_VALUE, &hKey);
396     if (lRet != ERROR_SUCCESS) {
397         error_code_messagebox(hwnd, lRet);
398         return FALSE;
399     }
400     
401     if (messagebox(hwnd, MB_YESNO | MB_ICONEXCLAMATION, IDS_DELETE_BOX_TITLE, IDS_DELETE_BOX_TEXT, keyPath) != IDYES)
402         goto done;
403         
404     lRet = SHDeleteKey(hKeyRoot, keyPath);
405     if (lRet != ERROR_SUCCESS) {
406         error(hwnd, IDS_BAD_KEY, keyPath);
407         goto done;
408     }
409     result = TRUE;
410     
411 done:
412     RegCloseKey(hKey);
413     return result;
414 }
415
416 BOOL DeleteValue(HWND hwnd, HKEY hKeyRoot, LPCTSTR keyPath, LPCTSTR valueName, BOOL showMessageBox)
417 {
418     BOOL result = FALSE;
419     LONG lRet;
420     HKEY hKey;
421     LPCSTR visibleValueName = valueName ? valueName : g_pszDefaultValueName;
422
423     lRet = RegOpenKeyEx(hKeyRoot, keyPath, 0, KEY_READ | KEY_SET_VALUE, &hKey);
424     if (lRet != ERROR_SUCCESS) return FALSE;
425
426     if (showMessageBox)
427         if (messagebox(hwnd, MB_YESNO | MB_ICONEXCLAMATION, IDS_DELETE_BOX_TITLE, IDS_DELETE_BOX_TEXT, visibleValueName) != IDYES)
428                 goto done;
429
430     lRet = RegDeleteValue(hKey, valueName ? valueName : "");
431     if (lRet != ERROR_SUCCESS && valueName) {
432         error(hwnd, IDS_BAD_VALUE, valueName);
433     }
434     if (lRet != ERROR_SUCCESS) goto done;
435     result = TRUE;
436
437 done:
438     RegCloseKey(hKey);
439     return result;
440 }
441
442 BOOL CreateValue(HWND hwnd, HKEY hKeyRoot, LPCTSTR keyPath, DWORD valueType, LPTSTR valueName)
443 {
444     LONG lRet = ERROR_SUCCESS;
445     TCHAR newValue[256];
446     DWORD valueDword = 0;
447     BOOL result = FALSE;
448     int valueNum;
449     HKEY hKey;
450          
451     lRet = RegOpenKeyEx(hKeyRoot, keyPath, 0, KEY_READ | KEY_SET_VALUE, &hKey);
452     if (lRet != ERROR_SUCCESS) {
453         error_code_messagebox(hwnd, lRet);
454         return FALSE;
455     }
456
457     if (!LoadString(GetModuleHandle(0), IDS_NEWVALUE, newValue, COUNT_OF(newValue))) goto done;
458
459     /* try to find out a name for the newly create key (max 100 times) */
460     for (valueNum = 1; valueNum < 100; valueNum++) {
461         wsprintf(valueName, newValue, valueNum);
462         lRet = RegQueryValueEx(hKey, valueName, 0, 0, 0, 0);
463         if (lRet == ERROR_FILE_NOT_FOUND) break;
464     }
465     if (lRet != ERROR_FILE_NOT_FOUND) {
466         error_code_messagebox(hwnd, lRet);
467         goto done;
468     }
469    
470     lRet = RegSetValueEx(hKey, valueName, 0, valueType, (BYTE*)&valueDword, sizeof(DWORD));
471     if (lRet != ERROR_SUCCESS) {
472         error_code_messagebox(hwnd, lRet);
473         goto done;
474     }
475     result = TRUE;
476
477 done:
478     RegCloseKey(hKey);
479     return result;
480 }
481
482 BOOL RenameValue(HWND hwnd, HKEY hKeyRoot, LPCTSTR keyPath, LPCTSTR oldName, LPCTSTR newName)
483 {
484     LPTSTR value = NULL;
485     DWORD type;
486     LONG len, lRet;
487     BOOL result = FALSE;
488     HKEY hKey;
489
490     if (!oldName) return FALSE;
491     if (!newName) return FALSE;
492
493     lRet = RegOpenKeyEx(hKeyRoot, keyPath, 0, KEY_READ | KEY_SET_VALUE, &hKey);
494     if (lRet != ERROR_SUCCESS) {
495         error_code_messagebox(hwnd, lRet);
496         return FALSE;
497     }
498     /* check if value already exists */
499     if (check_value(hwnd, hKey, newName)) goto done;
500     value = read_value(hwnd, hKey, oldName, &type, &len);
501     if(!value) goto done;
502     lRet = RegSetValueEx(hKey, newName, 0, type, (BYTE*)value, len);
503     if (lRet != ERROR_SUCCESS) {
504         error_code_messagebox(hwnd, lRet);
505         goto done;
506     }
507     lRet = RegDeleteValue(hKey, oldName);
508     if (lRet != ERROR_SUCCESS) {
509         RegDeleteValue(hKey, newName);
510         error_code_messagebox(hwnd, lRet);
511         goto done;
512     }
513     result = TRUE;
514
515 done:
516     HeapFree(GetProcessHeap(), 0, value);
517     RegCloseKey(hKey);
518     return result;
519 }
520
521
522 BOOL RenameKey(HWND hwnd, HKEY hRootKey, LPCTSTR keyPath, LPCTSTR newName)
523 {
524     LPTSTR parentPath = 0;
525     LPCTSTR srcSubKey = 0;
526     HKEY parentKey = 0;
527     HKEY destKey = 0;
528     BOOL result = FALSE;
529     LONG lRet;
530     DWORD disposition;
531
532     if (!keyPath || !newName) return FALSE;
533
534     if (!strrchr(keyPath, '\\')) {
535         parentKey = hRootKey;
536         srcSubKey = keyPath;
537     } else {
538         LPTSTR srcSubKey_copy;
539
540         parentPath = strdup(keyPath);
541         srcSubKey_copy = strrchr(parentPath, '\\');
542         *srcSubKey_copy = 0;
543         srcSubKey = srcSubKey_copy + 1;
544         lRet = RegOpenKeyEx(hRootKey, parentPath, 0, KEY_READ | KEY_CREATE_SUB_KEY, &parentKey);
545         if (lRet != ERROR_SUCCESS) {
546             error_code_messagebox(hwnd, lRet);
547             goto done;
548         }
549     }
550
551     /* The following fails if the old name is the same as the new name. */
552     if (!strcmp(srcSubKey, newName)) goto done;
553
554     lRet = RegCreateKeyEx(parentKey, newName, 0, NULL, REG_OPTION_NON_VOLATILE,
555         KEY_WRITE, NULL /* FIXME */, &destKey, &disposition);
556     if (disposition == REG_OPENED_EXISTING_KEY)
557         lRet = ERROR_FILE_EXISTS; /* FIXME: we might want a better error message than this */
558     if (lRet != ERROR_SUCCESS) {
559         error_code_messagebox(hwnd, lRet);
560         goto done;
561     }
562
563     /* FIXME: SHCopyKey does not copy the security attributes */
564     lRet = SHCopyKey(parentKey, srcSubKey, destKey, 0);
565     if (lRet != ERROR_SUCCESS) {
566         RegCloseKey(destKey);
567         RegDeleteKey(parentKey, newName);
568         error_code_messagebox(hwnd, lRet);
569         goto done;
570     }
571
572     lRet = SHDeleteKey(hRootKey, keyPath);
573     if (lRet != ERROR_SUCCESS) {
574         error_code_messagebox(hwnd, lRet);
575         goto done;
576     }
577
578     result = TRUE;
579
580 done:
581     RegCloseKey(destKey);
582     if (parentKey) {
583         RegCloseKey(parentKey); 
584         free(parentPath);
585     }
586     return result;
587 }