1 /*
2  * regexpl - Console Registry Explorer
3  *
4  * Copyright (C) 2000-2005 Nedko Arnaudov <nedko@users.sourceforge.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; see the file COPYING.  If not, write to
18  * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21 
22 // Completion.cpp : Defines the completion related functions.
23 #include "ph.h"
24 #include "RegistryKey.h"
25 #include "RegistryTree.h"
26 
27 #define COMPLETION_BUFFER_SIZE	4096
28 
29 extern CRegistryTree Tree;
30 
31 BOOL g_blnCompletionCycle = TRUE;
32 
33 class CCompletionMatch
34 {
35 public:
36   CCompletionMatch()
37   {
38     m_pNext = m_pPrev = NULL;
39     m_pszText = NULL;
40   };
41 
42   BOOL Init(const TCHAR *pszText)
43   {
44     // find the offset to "unique" part
45     const TCHAR *pszUnique = _tcsrchr(pszText,_T('\\'));
46     pszUnique = pszUnique?(pszUnique+1):pszText;
47     BOOL b = _tcschr(pszUnique,_T(' ')) != NULL; // has it spaces in it ???
48     size_t s = _tcslen(pszText);
49 
50     if (m_pszText)
51       delete[] m_pszText;
52 
53     m_pszText = new (std::nothrow) TCHAR [s+(b?3:1)]; // if we have spaces in unique part, we need 2 addtional chars for "
54 
55     if (!m_pszText)
56       return FALSE;
57 
58     ASSERT(pszText <= pszUnique);
59     s = pszUnique - pszText; // calculate offset of the unique part
60     if (s)
61       _tcsncpy(m_pszText,pszText,s);
62 
63     if (b)
64       m_pszText[s++] = _T('\"');
65 
66     _tcscpy(m_pszText+s,pszUnique);
67 
68     if (b)
69       _tcscat(m_pszText,_T("\""));
70 
71     return TRUE;
72   }
73 
74   ~CCompletionMatch()
75   {
76     if (m_pszText)
77       delete[] m_pszText;
78   }
79 
80 private:
81   TCHAR *m_pszText;
82   BOOL m_blnIsKey;
83   CCompletionMatch *m_pNext;
84   CCompletionMatch *m_pPrev;
85   friend class CCompletionList;
86 };
87 
88 class CCompletionList
89 {
90 public:
91   CCompletionList();
92   ~CCompletionList();
93 
94   BOOL IsNewCompletion(const TCHAR *pszContext, const TCHAR *pszBegin, BOOL& rblnNew);
95   BOOL Add(const TCHAR *pszText, BOOL blnIsKey);
96   const TCHAR *Get(unsigned __int64 nIndex, BOOL& rblnIsKey);
97   unsigned __int64 GetCount();
98   const TCHAR * GetContext();
99   const TCHAR * GetBegin();
100   void DeleteList();
101   void Invalidate();
102 
103 private:
104   CCompletionMatch *m_pHead; // head of completions linked list
105   CCompletionMatch *m_pTail; // tail of completions linked list
106   CCompletionMatch *m_pLastSearched;
107   unsigned int m_nLastSearched;
108   TCHAR *m_pszContext;
109   TCHAR *m_pszBegin;
110   TCHAR *m_pszCurrentKey;
111   unsigned __int64 m_nCount;
112 } g_Completion;
113 
114 // --- begin of CCompletionList implementation ---
115 CCompletionList::CCompletionList()
116 {
117   m_pHead = NULL;
118   m_pTail = NULL;
119   m_nCount = 0;
120   m_pLastSearched = NULL;
121   m_pszContext = NULL;
122   m_pszBegin = NULL;
123   m_pszCurrentKey = NULL;
124 }
125 
126 CCompletionList::~CCompletionList()
127 {
128   DeleteList();
129 
130   if (m_pszContext)
131     delete[] m_pszContext;
132 
133   if (m_pszBegin)
134     delete[] m_pszBegin;
135 
136   if (m_pszCurrentKey)
137     delete[] m_pszCurrentKey;
138 }
139 
140 void CCompletionList::Invalidate()
141 {
142   if (m_pszCurrentKey)
143   {
144     delete[] m_pszCurrentKey;
145     m_pszCurrentKey = NULL;
146   }
147 }
148 
149 BOOL CCompletionList::IsNewCompletion(const TCHAR *pszContext, const TCHAR *pszBegin, BOOL& rblnNew)
150 {
151   const TCHAR *pszCurrentKey = Tree.GetCurrentPath();
152   if (!m_pszContext ||
153       !m_pszBegin ||
154       !m_pszCurrentKey ||
155       (_tcscmp(m_pszContext,pszContext) != 0) ||
156       (_tcscmp(m_pszBegin,pszBegin) != 0) ||
157       (_tcscmp(m_pszCurrentKey,pszCurrentKey)))
158   { // new completion
159     DeleteList();
160     rblnNew = TRUE;
161     if (m_pszContext)
162     {
163       delete[] m_pszContext;
164       m_pszContext = NULL;
165     }
166 
167     if (m_pszBegin)
168     {
169       delete[] m_pszBegin;
170       m_pszBegin = NULL;
171     }
172 
173     if (m_pszCurrentKey)
174     {
175       delete[] m_pszCurrentKey;
176       m_pszCurrentKey = NULL;
177     }
178 
179     size_t s = _tcslen(pszContext);
180     m_pszContext = new (std::nothrow) TCHAR[s+1];
181     if (!m_pszContext)
182       return FALSE;
183     _tcscpy(m_pszContext,pszContext);
184 
185     s = _tcslen(pszBegin);
186     m_pszBegin = new (std::nothrow) TCHAR[s+1];
187     if (!m_pszBegin)
188     {
189       delete[] m_pszContext;
190       m_pszContext = NULL;
191       return FALSE;
192     }
193     _tcscpy(m_pszBegin,pszBegin);
194 
195     s = _tcslen(pszCurrentKey);
196     m_pszCurrentKey = new (std::nothrow) TCHAR[s+1];
197     if (!m_pszCurrentKey)
198     {
199       delete[] m_pszContext;
200       delete[] m_pszBegin;
201       m_pszContext = NULL;
202       m_pszBegin = NULL;
203       return FALSE;
204     }
205     _tcscpy(m_pszCurrentKey,pszCurrentKey);
206 
207     return TRUE;
208   }
209 
210   rblnNew = FALSE;
211   return TRUE;
212 }
213 
214 BOOL CCompletionList::Add(const TCHAR *pszText, BOOL blnIsKey)
215 {
216   if (_tcsnicmp(pszText,m_pszBegin,_tcslen(m_pszBegin)) != 0)
217     return TRUE;
218   CCompletionMatch *pNode = new (std::nothrow) CCompletionMatch;
219   if (!pNode)
220     return FALSE;
221   if (!pNode->Init(pszText))
222     return FALSE;
223 
224   ASSERT(pNode->m_pszText);
225   ASSERT(pNode->m_pNext == NULL);
226 
227   // add new node to tail
228   pNode->m_blnIsKey = blnIsKey;
229   if (m_pTail)
230   {
231     pNode->m_pPrev = m_pTail;
232     ASSERT(m_pTail->m_pNext == NULL);
233     m_pTail->m_pNext = pNode;
234     m_pTail = pNode;
235   }
236   else
237   {
238     ASSERT(m_pHead == NULL);
239     m_pHead = m_pTail = pNode;
240   }
241 
242   m_nCount++;
243 
244   m_pLastSearched = NULL;
245 
246   return TRUE;
247 }
248 
249 const TCHAR * CCompletionList::Get(unsigned __int64 nIndex, BOOL& rblnIsKey)
250 {
251   ASSERT(nIndex < m_nCount);
252   BOOL blnForward = FALSE;
253   CCompletionMatch *pNode = NULL;
254 
255   unsigned __int64 nRelativeIndex = 0;
256 
257   if (m_pLastSearched)
258   {
259     pNode = m_pLastSearched;
260     blnForward = nIndex > m_nLastSearched;
261     nRelativeIndex = blnForward?(nIndex-m_nLastSearched):(m_nLastSearched-nIndex);
262     if ((nRelativeIndex > nIndex)||(nRelativeIndex > m_nCount-nIndex-1))
263       pNode = NULL; // seraching from tail or from head is more effective
264   }
265 
266   if (!pNode && (nIndex <= m_nCount/2))
267   { // search from head
268     pNode = m_pHead;
269     blnForward = TRUE;
270     nRelativeIndex = nIndex;
271   }
272 
273   if (!pNode)
274   { // search from tail
275     pNode = m_pTail;
276     blnForward = FALSE;
277     nRelativeIndex = m_nCount-nIndex-1;
278   }
279 
280   while(pNode)
281   {
282     if (nRelativeIndex == 0)
283     {
284       m_nLastSearched = nIndex;
285       m_pLastSearched = pNode;
286       rblnIsKey = pNode->m_blnIsKey;
287       if (!pNode->m_pszText)
288         ASSERT(FALSE);
289       return pNode->m_pszText;
290     }
291 
292     nRelativeIndex--;
293 
294     pNode = blnForward?(pNode->m_pNext):(pNode->m_pPrev);
295   }
296 
297   ASSERT(FALSE);
298   return NULL;
299 }
300 
301 unsigned __int64 CCompletionList::GetCount()
302 {
303   return m_nCount;
304 }
305 
306 const TCHAR * CCompletionList::GetContext()
307 {
308   return m_pszContext;
309 }
310 
311 const TCHAR * CCompletionList::GetBegin()
312 {
313   return m_pszBegin;
314 }
315 
316 void CCompletionList::DeleteList()
317 {
318   CCompletionMatch *pNode;
319   while(m_pHead)
320   {
321     pNode = m_pHead;
322     m_pHead = m_pHead->m_pNext;
323     delete pNode;
324   }
325   m_pTail = NULL;
326   ASSERT(m_pHead == NULL);
327   m_nCount = 0;
328 }
329 
330 // --- end of CCompletionList implementation ---
331 
332 BOOL FillCompletion(const TCHAR *pszKey)
333 {
334   g_Completion.DeleteList();
335   LONG nError;
336   CRegistryKey Key;
337   TCHAR *pszSubkeyName = NULL;
338   DWORD dwMaxSubkeyNameLength;
339   TCHAR *pszValueName = NULL;
340   DWORD dwMaxValueNameSize;
341 
342   if (!Tree.GetKey(pszKey?pszKey:_T("."),KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE,Key))
343     return FALSE;
344 
345   BOOL blnCompletionOnKeys = TRUE;
346   BOOL blnCompletionOnValues = TRUE;
347 
348 /*	if ((_tcsnicmp(pchContext,DIR_CMD,DIR_CMD_LENGTH) == 0)||
349 		(_tcsnicmp(pchContext,CD_CMD,CD_CMD_LENGTH) == 0)||
350 		(_tcsnicmp(pchContext,OWNER_CMD,OWNER_CMD_LENGTH) == 0)||
351 		(_tcsnicmp(pchContext,DACL_CMD,DACL_CMD_LENGTH) == 0)||
352 		(_tcsnicmp(pchContext,SACL_CMD,SACL_CMD_LENGTH) == 0))
353 	{
354 		blnCompletionOnValues = FALSE;
355 	}*/
356 //	else if (_tcsnicmp(pchContext,VALUE_CMD,VALUE_CMD_LENGTH) == 0)
357 //	{
358 //		blnCompletionOnKeys = FALSE;
359 //	}
360 
361   size_t nKeyNameSize = 0;
362   if (pszKey)
363   {
364     nKeyNameSize = _tcslen(pszKey);
365     if (_tcscmp(pszKey,_T("\\")))
366       nKeyNameSize++;
367   }
368 
369   if (blnCompletionOnKeys)
370   {
371     nError = Key.GetSubkeyNameMaxLength(dwMaxSubkeyNameLength);
372     if (nError != ERROR_SUCCESS)
373       return FALSE;
374 
375     pszSubkeyName = new (std::nothrow) TCHAR[nKeyNameSize+dwMaxSubkeyNameLength+1];
376     if (!pszSubkeyName)
377       goto Abort;
378 
379     if (pszKey)
380       _stprintf(pszSubkeyName,_tcscmp(pszKey,_T("\\"))?_T("%s\\"):_T("%s"),pszKey);
381 
382     Key.InitSubkeyEnumeration(pszSubkeyName+nKeyNameSize,dwMaxSubkeyNameLength);
383     while ((nError = Key.GetNextSubkeyName()) == ERROR_SUCCESS)
384       if (!g_Completion.Add(pszSubkeyName,TRUE))
385       {
386         ASSERT(FALSE);
387         goto Abort;
388       }
389     if ((nError != ERROR_SUCCESS)&&(nError != ERROR_NO_MORE_ITEMS))
390     {
391       ASSERT(FALSE);
392       goto Abort;
393     }
394   }
395 
396   if (blnCompletionOnValues)
397   {
398     nError = Key.GetMaxValueNameLength(dwMaxValueNameSize);
399     if (nError != ERROR_SUCCESS)
400     {
401       ASSERT(FALSE);
402       goto Abort;
403     }
404 
405     pszValueName = new (std::nothrow) TCHAR[nKeyNameSize+dwMaxValueNameSize+1];
406     if (!pszValueName)
407       goto Abort;
408 
409     if (pszKey)
410       _stprintf(pszValueName,_tcscmp(pszKey,_T("\\"))?_T("%s\\"):_T("%s"),pszKey);
411 
412     Key.InitValueEnumeration(pszValueName+nKeyNameSize,dwMaxValueNameSize,NULL,0,NULL);
413     while((nError = Key.GetNextValue()) == ERROR_SUCCESS)
414       if (!g_Completion.Add(pszValueName,FALSE))
415       {
416         ASSERT(FALSE);
417         goto Abort;
418       }
419     if ((nError != ERROR_SUCCESS)&&(nError != ERROR_NO_MORE_ITEMS))
420     {
421       ASSERT(FALSE);
422       goto Abort;
423     }
424   }
425 
426   if (pszValueName)
427     delete[] pszValueName;
428   if (pszSubkeyName)
429     delete[] pszSubkeyName;
430   return TRUE;
431 Abort:
432   if (pszValueName)
433     delete[] pszValueName;
434   if (pszSubkeyName)
435     delete[] pszSubkeyName;
436   return FALSE;
437 }
438 
439 const TCHAR * CompletionCallback(unsigned __int64 & rnIndex,
440                                  const BOOL *pblnForward,
441                                  const TCHAR *pszContext,
442                                  const TCHAR *pszBegin)
443 {
444   static TCHAR pszBuffer[COMPLETION_BUFFER_SIZE];
445 
446   // Find first non-white space in context
447   while(*pszContext && _istspace(*pszContext))
448     pszContext++;
449 
450   BOOL blnNewCompletion = TRUE;
451   if (!g_Completion.IsNewCompletion(pszContext,pszBegin,blnNewCompletion))
452   {
453     ASSERT(FALSE);
454     return NULL;
455   }
456 
457   if (blnNewCompletion)
458   {
459     _tcsncpy(pszBuffer,pszBegin,COMPLETION_BUFFER_SIZE-1);
460     pszBuffer[COMPLETION_BUFFER_SIZE-1] = 0;
461     TCHAR *pszSeparator = pszBuffer; // set it to aby non null value
462     if (_tcscmp(pszBuffer,_T("\\")))
463     {
464       pszSeparator = _tcsrchr(pszBuffer,_T('\\'));
465       if (pszSeparator)
466         *pszSeparator = 0;
467     }
468 
469     if (!FillCompletion(pszSeparator?pszBuffer:NULL))
470       return NULL;
471   }
472 
473   unsigned __int64 nTotalItems = g_Completion.GetCount();
474   if (nTotalItems == 0)
475     return NULL;
476 
477   if (rnIndex >= nTotalItems)
478     rnIndex = nTotalItems-1;
479 
480   if (pblnForward)
481   {
482     if (*pblnForward)
483     {
484       rnIndex++;
485       if (rnIndex >= nTotalItems)
486       {
487         if (g_blnCompletionCycle)
488           rnIndex = 0;
489         else
490           rnIndex--;
491       }
492     }
493     else
494     {
495       if (rnIndex)
496         rnIndex--;
497       else if (g_blnCompletionCycle)
498         rnIndex = nTotalItems-1;
499     }
500   }
501   BOOL blnIsKey = FALSE;
502   const TCHAR *pszName = g_Completion.Get(rnIndex,blnIsKey);
503 
504   ASSERT(pszName);
505 
506   _sntprintf(pszBuffer,COMPLETION_BUFFER_SIZE-1,_T("%s%s"),pszName,(blnIsKey?_T("\\"):_T("")));
507   pszBuffer[COMPLETION_BUFFER_SIZE-1] = 0;
508   return pszBuffer;
509 }
510 
511 void InvalidateCompletion()
512 {
513   g_Completion.Invalidate();
514 }
515