1 /*
2  * vile:notabinsert sw=4:
3  *
4  * wvwrap.cpp:  A WinVile WRAPper .
5  *
6  * Originally written by Ed Henderson.
7  *
8  * This wrapper may be used to open one or more files via a right mouse
9  * click in the Windows Explorer.  For more details, please read
10  * doc/oleauto.doc .
11  *
12  * Note:  A great deal of the code included in this file is copied
13  * (almost verbatim) from other vile modules.
14  *
15  * $Id: wvwrap.cpp,v 1.23 2016/07/27 09:28:11 tom Exp $
16  */
17 
18 #include "w32vile.h"
19 
20 #include <objbase.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <ctype.h>
24 
25 #include <initguid.h>
26 
27 #include "w32reg.h"
28 #include "w32ole.h"
29 #include "vl_alloc.h"
30 #include "makeargv.h"
31 
32 #define DQUOTE '"'
33 #define SQUOTE '\''
34 
35 static size_t olebuf_len;	/* scaled in wchar_t */
36 static OLECHAR *olebuf;
37 
38 #ifdef OPT_TRACE
39 #define Trace MyTrace
40 static void
Trace(const char * fmt,...)41 Trace(const char *fmt,...)
42 {
43     FILE *fp = fopen("c:\\temp\\wvwrap.log", "a");
44     if (fp != 0) {
45 	va_list ap;
46 	va_start(ap, fmt);
47 	vfprintf(fp, fmt, ap);
48 	va_end(ap);
49 	fclose(fp);
50     }
51 }
52 
53 #define TRACE(params) Trace params
54 #else
55 #define OPT_TRACE 0
56 #define TRACE(params)		/* nothing */
57 #endif
58 
59 //--------------------------------------------------------------
60 
61 /* WINVER >= _0x0500 */
62 #ifndef WC_NO_BEST_FIT_CHARS
63 #define WC_NO_BEST_FIT_CHARS 0
64 #endif
65 
66 static char *
asc_charstring(const LPTSTR source)67 asc_charstring(const LPTSTR source)
68 {
69     char *target = 0;
70 
71     if (source != 0) {
72 #if (defined(_UNICODE) || defined(UNICODE))
73 	ULONG len = WideCharToMultiByte(CP_ACP,
74 					WC_NO_BEST_FIT_CHARS,
75 					source,
76 					-1,
77 					0,
78 					0,
79 					NULL,
80 					NULL);
81 	if (len) {
82 	    target = typecallocn(char, len + 1);
83 
84 	    (void) WideCharToMultiByte(CP_ACP,
85 				       WC_NO_BEST_FIT_CHARS,
86 				       source,
87 				       -1,
88 				       target,
89 				       len,
90 				       NULL,
91 				       NULL);
92 	}
93 #else
94 	unsigned len = strlen(source) + 1;
95 	target = typecallocn(char, len + 1);
96 	if (target != 0)
97 	    memcpy(target, source, len);
98 #endif
99     }
100 
101     return target;
102 }
103 static LPTSTR
w32_charstring(const char * source)104 w32_charstring(const char *source)
105 {
106     TCHAR *target = 0;
107 
108     if (source != 0) {
109 #if (defined(_UNICODE) || defined(UNICODE))
110 	ULONG len = MultiByteToWideChar(CP_ACP,
111 					MB_USEGLYPHCHARS | MB_PRECOMPOSED,
112 					source,
113 					-1,
114 					0,
115 					0);
116 	if (len != 0) {
117 	    target = typecallocn(TCHAR, len + 1);
118 
119 	    (void) MultiByteToWideChar(CP_ACP,
120 				       MB_USEGLYPHCHARS | MB_PRECOMPOSED,
121 				       source,
122 				       -1,
123 				       target,
124 				       len);
125 	}
126 #else
127 	unsigned len = strlen(source) + 1;
128 	target = typecallocn(TCHAR, len + 1);
129 	if (target != 0)
130 	    memcpy(target, source, len);
131 #endif
132     }
133 
134     return target;
135 }
136 
137 /* from w32misc.c */
138 static LPTSTR
w32_prognam(void)139 w32_prognam(void)
140 {
141     return W32_STRING("wvwrap");
142 }
143 
144 int
w32_message_box(HWND hwnd,const char * message,int code)145 w32_message_box(HWND hwnd, const char *message, int code)
146 {
147     int rc;
148     LPTSTR buf = w32_charstring(message);
149 
150     rc = MessageBox(hwnd, buf, w32_prognam(), code);
151     free((void *) buf);
152     return (rc);
153 }
154 
155 static char *
fmt_win32_error(ULONG errcode,char ** buf,ULONG buflen)156 fmt_win32_error(ULONG errcode, char **buf, ULONG buflen)
157 {
158     int flags = FORMAT_MESSAGE_FROM_SYSTEM;
159     LPTSTR result = 0;
160 
161     if (*buf) {
162 	result = typeallocn(TCHAR, buflen + 1);
163     } else {
164 	flags |= FORMAT_MESSAGE_ALLOCATE_BUFFER;
165     }
166 
167     FormatMessage(flags,
168 		  NULL,
169 		  errcode,
170 		  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),	/* dflt language */
171 		  result,
172 		  buflen,
173 		  NULL);
174 
175     if (*buf != 0) {
176 	char *tmp = asc_charstring(result);
177 	strcpy(*buf, tmp);
178 	free(tmp);
179     }
180     return (*buf);
181 }
182 
183 static int
nomem(void)184 nomem(void)
185 {
186     char buf[512], *tmp;
187 
188     tmp = buf;
189     fmt_win32_error((ULONG) E_OUTOFMEMORY, &tmp, 0);
190     w32_message_box(NULL, buf, MB_OK | MB_ICONSTOP);
191     return (1);
192 }
193 
194 #if !(defined(_UNICODE) || defined(UNICODE))
195 /*
196  * Quick & Dirty Unicode conversion routine.  Routine uses a
197  * dynamic buffer to hold the converted string so it may be any arbitrary
198  * size.  However, the same dynamic buffer is reused when the routine is
199  * called a second time.  So make sure that the converted string is
200  * used/copied before the conversion routine is called again.
201  *
202  * from w32ole.cpp
203  */
204 static OLECHAR *
ConvertToUnicode(const char * szA)205 ConvertToUnicode(const char *szA)
206 {
207     size_t len;
208 
209     len = strlen(szA) + 1;
210     if (len > olebuf_len) {
211 	if (olebuf)
212 	    free(olebuf);
213 	olebuf_len = olebuf_len * 2 + len;
214 	if ((olebuf = typeallocn(OLECHAR, olebuf_len)) == NULL)
215 	    return (olebuf);	/* We're gonna' die */
216     }
217     mbstowcs(olebuf, szA, len);
218     return (olebuf);
219 }
220 #endif
221 
222 /*
223  * vile's prompts for directory/filename do not expect to strip quotes; they
224  * use the strings exactly as given.  At the same time, the initial "cd" and
225  * "e" commands use the token parsing which stops on a blank.  That makes it
226  * not simple to use OLE to send characters to the server to specify filenames
227  * containing blanks.
228  *
229  * We work around the problem using the "eval" command, which forces a reparse
230  * of the whole line.  That uses an extra level of interpretation, so we have
231  * to double each single quote _twice_ to enter single quotes in the filename.
232  *
233  * We turn off globbing to prevent vile from expanding dollar-signs and square
234  * brackets.
235  *
236  * Obscure, but it works.
237  */
238 static char *
escape_quotes(const char * src)239 escape_quotes(const char *src)
240 {
241     size_t len = 4 * strlen(src) + 1;
242     char *result = typeallocn(char, len);
243 
244     if (result == 0)
245 	exit(nomem());
246 
247     char *dst = result;
248     while (*src != '\0') {
249 	UCHAR ch = (UCHAR) * src;
250 	// only send ASCII, do not rely on the runtime to guess how to handle
251 	// non-ASCII characters.
252 	if (ch > 127) {
253 	    sprintf(dst, "\026x%02x", ch);
254 	    dst += 4;
255 	    src += 1;
256 	    continue;
257 	} else {
258 	    if (*src == SQUOTE) {
259 		*dst++ = SQUOTE;
260 		*dst++ = SQUOTE;
261 		*dst++ = SQUOTE;
262 	    }
263 	    *dst++ = *src++;
264 	}
265     }
266     *dst = '\0';
267 
268     return result;
269 }
270 
271 static void
append(char * & buffer,size_t & length,char * value)272 append(char *&buffer, size_t &length, char *value)
273 {
274     size_t newsize = strlen(buffer) + strlen(value);
275     if ((newsize + 1) > length) {
276 	length = (newsize + 1) * 2;
277 	buffer = (char *) realloc(buffer, length);
278     }
279     strcat(buffer, value);
280 }
281 
282 #define MY_BUFSIZ 4096
283 
284 int WINAPI
WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)285 WinMain(HINSTANCE hInstance,	// handle to current instance
286 	HINSTANCE hPrevInstance,	// handle to previous instance
287 	LPSTR lpCmdLine,	// pointer to command line
288 	int nCmdShow)		// show state of window
289 {
290     BSTR bstr;
291     size_t dynbuf_len;
292     HRESULT hr;
293     HWND hwnd;
294     VARIANT_BOOL insert_mode, glob_mode, minimized;
295     char *lclbuf = NULL, tmp[512], *dynbuf;
296     OLECHAR *olestr;
297     LPUNKNOWN punk;
298     IVileAuto *pVileAuto;
299     char **argv;
300     int argc;
301     int argc1;
302 
303     if (make_argv(0, lpCmdLine, &argv, &argc, NULL) < 0)
304 	return (nomem());
305     argc1 = after_options(0, argc, argv);
306 
307 #if OPT_TRACE
308     Trace("; cmdline:%s\n", lpCmdLine);
309     for (int n = 0; n < argc; ++n)
310 	Trace("; %sargv%d:%s\n", (n >= argc1) ? "*" : "", n, argv[n]);
311 #endif
312 
313     olebuf_len = MY_BUFSIZ;
314     dynbuf_len = MY_BUFSIZ;
315     olebuf = typeallocn(OLECHAR, olebuf_len);
316     dynbuf = typeallocn(char, dynbuf_len);
317     if (!(olebuf && dynbuf))
318 	return (nomem());
319 
320     hr = CoInitialize(NULL);	// Fire up COM.
321     if (FAILED(hr)) {
322 	fmt_win32_error(hr, &lclbuf, 0);
323 	sprintf(tmp,
324 		"ERROR: CoInitialize() failed, errcode: %#lx, desc: %s",
325 		hr,
326 		lclbuf);
327 	w32_message_box(NULL, tmp, MB_OK | MB_ICONSTOP);
328 	return (1);
329     }
330     hr = CoCreateInstance(CLSID_VileAuto,
331 			  NULL,
332 			  CLSCTX_LOCAL_SERVER,
333 			  IID_IUnknown,
334 			  (void **) &punk);
335     if (FAILED(hr)) {
336 	fmt_win32_error(hr, &lclbuf, 0);
337 	sprintf(tmp,
338 		"ERROR: CoInitialize() failed, errcode: %#lx, desc: %s\n"
339 		"Try registering winvile via its -Or switch.",
340 		hr,
341 		lclbuf);
342 	w32_message_box(NULL, tmp, MB_OK | MB_ICONSTOP);
343 	return (1);
344     }
345     hr = punk->QueryInterface(IID_IVileAuto, (void **) &pVileAuto);
346     punk->Release();
347     if (FAILED(hr)) {
348 	fmt_win32_error(hr, &lclbuf, 0);
349 	sprintf(tmp,
350 		"ERROR: QueryInterface() failed, errcode: %#lx, desc: %s\n\n",
351 		hr,
352 		lclbuf);
353 	w32_message_box(NULL, tmp, MB_OK | MB_ICONSTOP);
354 	return (1);
355     }
356     pVileAuto->put_Visible(VARIANT_TRUE);	// May not be necessary
357     pVileAuto->get_InsertMode(&insert_mode);
358     pVileAuto->get_GlobMode(&glob_mode);
359     if (argc > argc1) {
360 	char *cp;
361 
362 	*dynbuf = '\0';
363 
364 	if (insert_mode)
365 	    append(dynbuf, dynbuf_len, "\033");
366 
367 #if OPT_TRACE
368 	/*
369 	 * Turn on tracing in the server, for debugging.
370 	 */
371 	append(dynbuf, dynbuf_len, ":setv $debug=true\n");
372 #endif
373 	/*
374 	 * Disable globbing to simplify quoting.
375 	 */
376 	append(dynbuf, dynbuf_len, ":set noglob\n");
377 
378 	/*
379 	 * When wvwrap starts up (and subsequently launches winvile), the
380 	 * editor's CWD is set to a path deep within the bowels of Windows.
381 	 * Not very useful.  Change CWD to the directory of the first
382 	 * file on the commandline.
383 	 */
384 	cp = strrchr(*argv, '\\');
385 	if (cp) {
386 	    int add_delim;
387 
388 	    *cp = '\0';
389 	    if (cp == *argv) {
390 		/* filename is of the form:  \<leaf> .  handle this. */
391 		append(dynbuf, dynbuf_len, ":cd \\\n");
392 	    } else {
393 		/*
394 		 * To add insult to injury, Windows Explorer has a habit of
395 		 * passing 8.3 filenames to wvwrap (noted on a win2K system and
396 		 * a FAT32 partition).  If the folder portion of the file's
397 		 * path happens to be in 8.3 format (i.e., a tilde included in
398 		 * the folder name), then 8.3 folder names appear in winvile's
399 		 * Recent Folders list.
400 		 *
401 		 * Needless to say, it's no fun trying to decipher 8.3 folder
402 		 * names.
403 		 */
404 
405 		LPTSTR the_arg = w32_charstring(*argv);
406 		TCHAR folder[FILENAME_MAX];
407 		char *fp;
408 
409 		if (GetLongPathName(the_arg, folder, sizeof(folder)) > 0)
410 		    fp = asc_charstring(folder);
411 		else
412 		    fp = asc_charstring(the_arg);
413 
414 		add_delim = (isalpha(fp[0]) && fp[1] == ':' && fp[2] == '\0');
415 
416 		/*
417 		 * With regard to the following code, note that the
418 		 * original file might be in the form "<drive>:\leaf", in
419 		 * which case *argv now points at "<drive>:" .  Recall that
420 		 * cd'ing to <drive>:  on a DOS/WIN32 host has special
421 		 * semantics (which we don't want).
422 		 */
423 		char temp[MY_BUFSIZ];
424 		sprintf(temp, ":eval cd '%s%s'\n",
425 			escape_quotes(fp),
426 			(add_delim) ? "\\" : "");
427 
428 		append(dynbuf, dynbuf_len, temp);
429 		free(fp);
430 		free(the_arg);
431 	    }
432 	    *cp = '\\';
433 	}
434 
435 	while (argc1 < argc) {
436 	    char temp[MY_BUFSIZ];
437 	    sprintf(temp, ":eval e '%s'\n", escape_quotes(argv[argc1++]));
438 	    append(dynbuf, dynbuf_len, temp);
439 	}
440 	if (glob_mode)
441 	    append(dynbuf, dynbuf_len, ":set glob\n");
442 
443 	TRACE(("; dynbuf:\n%s\n", dynbuf));
444 	olestr = TO_OLE_STRING(dynbuf);
445 	bstr = SysAllocString(olestr);
446 
447 	if (!(bstr && olestr))
448 	    return (nomem());
449 	pVileAuto->VileKeys(bstr);
450 
451 	SysFreeString(bstr);
452 	free(olestr);
453     }
454 
455     // Set foreground window using a method that's compatible with win2k
456     pVileAuto->get_MainHwnd((LONG *) & hwnd);
457     (void) SetForegroundWindow(hwnd);
458 
459     // If editor minimized, restore window
460     hr = pVileAuto->get_IsMinimized(&minimized);
461     if (SUCCEEDED(hr) && minimized)
462 	hr = pVileAuto->Restore();
463     pVileAuto->Release();
464     CoUninitialize();		// shut down COM
465     return (0);
466 }
467