Skip to content
Newer
Older
100644 576 lines (472 sloc) 15.9 KB
6041f16 Initial commit.
Nikolai Weibull authored
1 #include "stdafx.h"
2 #include "content-plugin.h"
3 #include "wdx-encoding.h"
4 #include "line-endings.h"
5 #include "encoding.h"
6
7 #include <strsafe.h>
8
9 /* Will be set to true ContentStopGetValue() if the ContentGetValue()
10 * procedure should be aborted as soon as possible. */
11 BOOL g_get_value_aborted;
12
13 /* The maximum number of bytes to map of a file. */
14 #define MAX_MAP_SIZE (256 * 1024)
15
16 /* The file name that the cached data of the fields refers to. */
17 static char *s_cached_filename;
18
19 /* The names of line endings. */
20 static char const * const line_ending_names[] = {
21 "-",
22 "LF",
23 "CR+LF",
24 "CR",
25 "LS",
26 "NEL",
27 };
28
29 typedef unsigned int iconv_t;
30
31 typedef iconv_t (*IconvOpenFunc)(char const *, char const *);
32 typedef size_t (*IconvFunc)(iconv_t, char const **, size_t *, char **, size_t *);
33 typedef int (*IconvCloseFunc)(iconv_t);
34
35 static IconvOpenFunc iconv_open;
36 static IconvFunc iconv;
37 static IconvCloseFunc iconv_close;
38
39 /* The indexes into the array of fields we provide. */
40 typedef enum FieldIndex
41 {
42 FieldIndexEncoding,
43 FieldIndexLineEnding,
44 };
45
46 /* A function associated with a field for setting that fields’ units. */
47 typedef void (*FieldSetUnitsFunc)(char *, int);
48
49 typedef TCFieldFlags (*FieldSetFlagsFunc)(void);
50
51 /* A field supplied by this plugin.
52 *
53 * NAME is the name of the field.
54 * SET_UNITS is the function used for setting the fields’ units.
55 * TYPE is the type of the field.
56 * IS_SLOW specifies whether this field is slow to retrieve or not.
57 * CACHED_DATA keeps the cached value for this field for S_CACHED_FILENAME. */
58 typedef struct _Field Field;
59
60 struct _Field
61 {
62 const char * const name;
63 FieldSetUnitsFunc set_units;
64 TCFieldTypeOrStatus type;
65 FieldSetFlagsFunc set_flags;
66 bool is_slow;
67 void const *cached_data;
68 };
69
70 /* Used as a helper method for joining strings together, separated
71 * by ‘|’ characters, as used by the content plugin interface for,
72 * for example, units. */
73 static void
74 StringsJoin(char *joined, int size, char const *string)
75 {
76 if (joined[0] == '\0') {
77 StringCbCopy(joined, size, string);
78 return;
79 }
80
81 StringCbCat(joined, size, "|");
82 StringCbCat(joined, size, string);
83 }
84
85 /* A closure used when iterating over encodings to generate a units
86 * specification for the “Encoding” field.
87 *
88 * UNITS keeps the joined string of units.
89 * SIZE is the maximum number of bytes we can fit in UNITS. */
90 typedef struct _EncodingFieldSetUnitsClosure EncodingFieldSetUnitsClosure;
91
92 struct _EncodingFieldSetUnitsClosure
93 {
94 char *units;
95 int size;
96 };
97
98 /* Iterator creating the units specification for the “Encoding” field. */
99 BOOL
100 EncodingFieldSetUnitsIterator(Encoding const *encoding, VOID *void_closure)
101 {
102 EncodingFieldSetUnitsClosure *closure = (EncodingFieldSetUnitsClosure *)void_closure;
103
104 StringsJoin(closure->units, closure->size, EncodingName(encoding));
105
106 return TRUE;
107 }
108
109 /* The FieldSetUnitsFunc used for the “Encoding” field. */
110 static void
111 EncodingFieldSetUnits(char *units, int size)
112 {
113 EncodingFieldSetUnitsClosure closure = { units, size };
114
115 EncodingsEach(EncodingFieldSetUnitsIterator, &closure);
116 }
117
118 /* The FieldSetUnitsFunc used for the “Line Endings” field. */
119 static void
120 LineEndingsFieldSetUnits(char *units, int size)
121 {
122 for (int i = 0; i < _countof(line_ending_names); i++)
123 StringsJoin(units, size, line_ending_names[i]);
124 }
125
126 static TCFieldFlags
127 EncodingFieldSetFlags(void)
128 {
129 return TCFieldFlagsEdit | TCFieldFlagsSubstAttributeStr;
130 }
131
132 static TCFieldFlags
133 LineEndingsFieldSetFlags(void)
134 {
135 return TCFieldFlagsEdit | TCFieldFlagsSubstAttributeStr;
136 }
137
138 /* These are the fields that this plugin provides. */
139 Field s_fields[] = {
140 { "Encoding", EncodingFieldSetUnits, TCFieldTypeMultipleChoice, EncodingFieldSetFlags, TRUE },
141 { "Line Endings", LineEndingsFieldSetUnits, TCFieldTypeMultipleChoice, LineEndingsFieldSetFlags, TRUE },
142 };
143
144 /* This function is called by Total Commander to retrieve information
145 * about the fields provided by this plugin. */
146 TCFieldTypeOrStatus __stdcall
147 ContentGetSupportedField(int index, char *name, char *units, int size)
148 {
149 /* I really don’t know when INDEX would be less than 0, but the example
150 * plugin had this test in there, so we best keep it. (INDEX should be
151 * an unsigned int in my opinion.) */
152 if (index < 0 || index >= _countof(s_fields))
153 return TCFieldTypeNoMoreFields;
154
155 /* Make sure that the string is empty, so that calling StringsJoin will
156 * work properly. */
157 units[0] = '\0';
158
159 Field field = s_fields[index];
160
161 StringCbCopy(name, size, field.name);
162 field.set_units(units, size);
163
164 return field.type;
165 }
166
167 TCFieldFlags __stdcall
168 ContentGetSupportedFieldFlags(int index)
169 {
170 if (index < 0 || index >= _countof(s_fields))
171 return TCFieldFlagsEdit | TCFieldFlagsSubstMask;
172
173 return s_fields[index].set_flags();
174 }
175
176 /* Checks if the cache contains an entry for FILENAME. */
177 static BOOL
178 CacheContains(char const *filename)
179 {
180 return lstrcmpi(s_cached_filename, filename) == 0;
181 }
182
183 /* Clears the cached data retained in the fields. */
184 static void
185 CacheClear(void)
186 {
187 for (size_t i = 0; i < _countof(s_fields); i++)
188 s_fields[i].cached_data = NULL;
189
190 if (s_cached_filename == NULL)
191 return;
192
193 HeapFree(GetProcessHeap(), 0, s_cached_filename);
194 s_cached_filename = NULL;
195 }
196
197 /* Retrieves the given fields cached value, if one exists. */
198 static TCFieldTypeOrStatus
199 CacheGet(int field_index, void *field_value, int field_value_size)
200 {
201 Field field = s_fields[field_index];
202
203 if (field.cached_data == NULL)
204 return TCFieldStatusFieldEmpty;
205
206 /* TODO: Finish up with the other types of fields that we can have. */
207 switch (field.type) {
208 #if 0
209 case TCFieldTypeNumeric32:
210 *((int *)field_value) = (int)field.cached_data;
211 break;
212 case TCFieldTypeNumeric64:
213 *((__int64 *)field_value) = *((_int64 *)field.cached_data);
214 break;
215 case TCFieldTypeNumericFloating:
216 *((double *)field_value) = (double)field.cached_data;
217 break;
218 case TCFieldTypeDate:
219 case TCFieldTypeTime:
220 break;
221 case TCFieldTypeBoolean:
222 *((BOOL *)field_value) = (BOOL)field.cached_data;
223 break;
224 #endif
225 case TCFieldTypeMultipleChoice:
226 #if 0
227 case TCFieldTypeString:
228 case TCFieldTypeFullText:
229 #endif
230 if (!SUCCEEDED(StringCbCopy((char *)field_value, field_value_size,
231 (char *)field.cached_data)))
232 return TCFieldStatusFieldEmpty;
233 break;
234 #if 0
235 case TCFieldTypeDateTime:
236 break;
237 #endif
238 default:
239 return TCFieldStatusFieldEmpty;
240 }
241
242 return field.type;
243 }
244
245 /* Stores field values in ENCODING associated with FILENAME in the cache. */
246 static void
247 CachePut(char const *filename, Encoding const *encoding, LineEnding line_ending)
248 {
249 CacheClear();
250
251 size_t filename_length = lstrlen(filename);
252 s_cached_filename = (char *)HeapAlloc(GetProcessHeap(), 0, filename_length + 1);
253 if (s_cached_filename == NULL)
254 return;
255
256 if (!SUCCEEDED(StringCbCopy(s_cached_filename, filename_length + 1, filename)))
257 CacheClear();
258
259 s_fields[FieldIndexEncoding].cached_data = EncodingName(encoding);
260 s_fields[FieldIndexLineEnding].cached_data = line_ending_names[line_ending];
261 }
262
263 typedef struct _FileMapping FileMapping;
264
265 struct _FileMapping
266 {
267 HANDLE file;
268 HANDLE map;
269 unsigned char const *bytes;
270 size_t n_bytes;
271 };
272
273 static TCFieldTypeOrStatus
274 MapFile(char const *filename, FileMapping *mapping, size_t max_size)
275 {
276 HANDLE file = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL,
277 OPEN_EXISTING,
278 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
279 NULL);
280 if (file == INVALID_HANDLE_VALUE)
281 return TCFieldStatusFileError;
282
283 LARGE_INTEGER file_size;
284 if (!GetFileSizeEx(file, &file_size) ||
285 (file_size.LowPart == 0 && file_size.HighPart == 0)) {
286 /* TODO: This should be the Unknown encoding. */
287 CloseHandle(file);
288 return TCFieldStatusFieldEmpty;
289 }
290
291 size_t n_bytes = max_size == 0 ? file_size.LowPart : min(file_size.LowPart, max_size);
292 HANDLE map = CreateFileMapping(file, NULL, PAGE_READONLY, 0, n_bytes, NULL);
293 if (map == NULL || GetLastError() == ERROR_ALREADY_EXISTS) {
294 CloseHandle(file);
295 return TCFieldStatusFileError;
296 }
297
298 /* TODO: This crashes when a (CSV) file is locked by microsoft excel when importing data. */
299
300 unsigned char *bytes = (unsigned char *)MapViewOfFile(map, FILE_MAP_READ,
301 0, 0, n_bytes);
302 MEMORY_BASIC_INFORMATION mbi;
303 if (bytes == NULL ||
304 VirtualQuery(bytes, &mbi, sizeof(mbi)) < sizeof(mbi) ||
305 mbi.State != MEM_COMMIT ||
306 mbi.BaseAddress != bytes ||
307 mbi.RegionSize < n_bytes) {
308 CloseHandle(map);
309 CloseHandle(file);
310 return TCFieldStatusFileError;
311 }
312
313 mapping->file = file;
314 mapping->map = map;
315 mapping->bytes = bytes;
316 mapping->n_bytes = n_bytes;
317
318 return TCFieldStatusSetSuccess;
319 }
320
321 static void
322 UnmapFile(FileMapping *mapping)
323 {
324 UnmapViewOfFile(mapping->bytes);
325 CloseHandle(mapping->map);
326 CloseHandle(mapping->file);
327 }
328
329 /* Called by Total Commander to get the value of field FIELD_INDEX for
330 * FILENAME. If units are being used for this field, UNIT_INDEX will
331 * point to the unit that the user has chosen to display the field in.
332 * FIELD_VALUE_SIZE is the maximum number of bytes we can store in
333 * FIELD_VALUE. FLAGS are any additional flags passed to us by Total
334 * Commander, such as the request to delay the calculation of a fields’
335 * value if it is slow to calculate (see Field.is_slow). */
336 TCFieldTypeOrStatus __stdcall
337 ContentGetValue(char *filename, int field_index, int unit_index,
338 void *field_value, int field_value_size, TCContentFlag flags)
339 {
340 /* I really don’t know when INDEX would be less than 0, but the example
341 * plugin had this test in there, so we best keep it. (INDEX should be
342 * an unsigned int in my opinion.) */
343 if (field_index < 0 || field_index >= _countof(s_fields))
344 return TCFieldTypeNoMoreFields;
345
346 g_get_value_aborted = FALSE;
347
348 if ((flags & TCContentFlagDelayIfSlow) && s_fields[field_index].is_slow)
349 return TCFieldStatusDelayed;
350
351 if (CacheContains(filename))
352 return CacheGet(field_index, field_value, field_value_size);
353
354 CacheClear();
355
356 FileMapping mapping;
357 TCFieldTypeOrStatus status = MapFile(filename, &mapping, MAX_MAP_SIZE);
358 if (status != TCFieldStatusSetSuccess)
359 return status;
360
361 Encoding const *encoding = EncodingFind(mapping.bytes, mapping.n_bytes);
362
363 CachePut(filename, encoding, EncodingLineEndings(encoding, mapping.bytes, mapping.n_bytes));
364
365 UnmapFile(&mapping);
366
367 return CacheGet(field_index, field_value, field_value_size);
368 }
369
370 /* Called by Total Commander when the user has elected to stop getting values
371 * of fields provided by this plugin. This is usually done when changing
372 * directories or the user press Escape. */
373 void __stdcall
374 ContentStopGetValue(char *filename)
375 {
376 UNREFERENCED_PARAMETER(filename);
377
378 g_get_value_aborted = TRUE;
379 }
380
381 static void
382 UnloadIconv(HMODULE iconv_dll)
383 {
384 iconv_open = NULL;
385 iconv = NULL;
386 iconv_close = NULL;
387
388 FreeLibrary(iconv_dll);
389 iconv_dll = NULL;
390 }
391
392 static HMODULE
393 LoadIconv(void)
394 {
395 HMODULE iconv_dll = LoadLibrary("iconv.dll");
396 if (iconv_dll == NULL)
397 return NULL;
398
399 iconv_open = (IconvOpenFunc)GetProcAddress(iconv_dll, "libiconv_open");
400 iconv = (IconvFunc)GetProcAddress(iconv_dll, "libiconv");
401 iconv_close = (IconvCloseFunc)GetProcAddress(iconv_dll, "libiconv_close");
402
403 if (iconv_open != NULL && iconv != NULL && iconv_close != NULL)
404 return iconv_dll;
405
406 UnloadIconv(iconv_dll);
407
408 return NULL;
409 }
410
411 static BOOL
412 GenerateTemporaryFileName(char *destination)
413 {
414 /* According to MSDN, GetTempFileName doesn’t allow LPPATHNAME to be more than
415 * MAX_PATH - 14 characters long. It doesn’t (of course, seeing as how this is
416 * MSDN) specify if this includes the terminating NULL, but let’s assume that it
417 * doesn’t. */
418 # define TEMP_PATH_LENGTH (MAX_PATH - 14 + 1)
419 char temp_path[TEMP_PATH_LENGTH];
420 DWORD actual_length = GetTempPath(TEMP_PATH_LENGTH, temp_path);
421 if (actual_length == 0 || actual_length > TEMP_PATH_LENGTH)
422 return FALSE;
423
424 if (GetTempFileName(temp_path, "ENC", 0, destination) == 0)
425 return FALSE;
426
427 return TRUE;
428 }
429
430 static TCFieldTypeOrStatus
431 IconvFile(char *filename, Encoding const *from, Encoding const *to)
432 {
433 if (EncodingIconvName(from) == NULL || EncodingIconvName(to) == NULL)
434 return TCFieldStatusFileError;
435
436 char temp_file_name[MAX_PATH + 1];
437 if (!GenerateTemporaryFileName(temp_file_name))
438 return TCFieldStatusFileError;
439
440 /* Why is there no STRSAFE_MAX_CB? */
441 size_t from_bom_length, to_bom_length;
442 if (FAILED(StringCbLength(EncodingBOM(from), STRSAFE_MAX_CCH, &from_bom_length)) ||
443 FAILED(StringCbLength(EncodingBOM(to), STRSAFE_MAX_CCH, &to_bom_length)))
444 return TCFieldStatusFileError;
445
446 iconv_t cd = iconv_open(EncodingIconvName(to), EncodingIconvName(from));
447 if (cd == (iconv_t)-1)
448 return TCFieldStatusFileError;
449
450 FileMapping input;
451 TCFieldTypeOrStatus status = MapFile(filename, &input, 0);
452 if (status != TCFieldStatusSetSuccess)
453 return status;
454
455 HANDLE output = CreateFile(temp_file_name, GENERIC_WRITE, 0, NULL,
456 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
457 if (output == INVALID_HANDLE_VALUE) {
458 UnmapFile(&input);
459 return TCFieldStatusFileError;
460 }
461
462 # define ICONVFILE_BUFFER_SIZE (32768)
463 char const *input_pointer = (char const *)input.bytes + from_bom_length;
464 char output_buffer[ICONVFILE_BUFFER_SIZE];
465 size_t remaining = input.n_bytes - from_bom_length;
466
467 DWORD bytes_written;
468 if (!WriteFile(output, EncodingBOM(to), to_bom_length, &bytes_written, NULL) ||
469 bytes_written != to_bom_length) {
470 UnmapFile(&input);
471 CloseHandle(output);
472 DeleteFile(temp_file_name);
473 return TCFieldStatusFileError;
474 }
475
476 while (remaining > 0) {
477 char *output_pointer = output_buffer;
478 size_t output_bytes_remaining = _countof(output_buffer);
479
480 size_t bytes_converted = iconv(cd, &input_pointer, &remaining, &output_pointer, &output_bytes_remaining);
481 if (output_pointer != output_buffer) {
482 DWORD bytes_to_write = output_pointer - output_buffer;
483 if (!WriteFile(output, output_buffer, bytes_to_write, &bytes_written, NULL) ||
484 bytes_written != bytes_to_write) {
485 UnmapFile(&input);
486 CloseHandle(output);
487 DeleteFile(temp_file_name);
488 return TCFieldStatusFileError;
489 }
490 }
491
492 if (bytes_converted != (size_t)-1) {
493 output_pointer = output_buffer;
494 output_bytes_remaining = _countof(output_buffer);
495
496 bytes_converted = iconv(cd, NULL, NULL, &output_pointer, &output_bytes_remaining);
497 if (output_pointer != output_buffer) {
498 DWORD bytes_to_write = output_pointer - output_buffer;
499 if (!WriteFile(output, output_buffer, bytes_to_write, &bytes_written, NULL) ||
500 bytes_written != bytes_to_write) {
501 UnmapFile(&input);
502 CloseHandle(output);
503 DeleteFile(temp_file_name);
504 return TCFieldStatusFileError;
505 }
506 }
507
508 if (bytes_converted == (size_t)-1) {
509 UnmapFile(&input);
510 CloseHandle(output);
511 DeleteFile(temp_file_name);
512 return TCFieldStatusFileError;
513 }
514 } else {
515 UnmapFile(&input);
516 CloseHandle(output);
517 DeleteFile(temp_file_name);
518 return TCFieldStatusFileError;
519 }
520 }
521
522 UnmapFile(&input);
523 CloseHandle(output);
524 CopyFile(temp_file_name, filename, FALSE);
525 DeleteFile(temp_file_name);
526 /* TODO: Should really restore other attributes, like time and such. */
527
528 return TCFieldStatusSetSuccess;
529 }
530
531 TCFieldTypeOrStatus __stdcall
532 ContentSetValue(char *filename, int field_index, int unit_index,
533 TCFieldTypeOrStatus field_type, void *field_value,
534 TCContentSetValueFlags flags)
535 {
536 if (field_index == FieldIndexLineEnding)
537 return TCFieldStatusFileError;
538
539 Encoding const *new_encoding = EncodingsGet(unit_index);
540 if (new_encoding == NULL)
541 return TCFieldStatusNoSuchField;
542
543 FileMapping mapping;
544 TCFieldTypeOrStatus status = MapFile(filename, &mapping, MAX_MAP_SIZE);
545 if (status != TCFieldStatusSetSuccess)
546 return status;
547
548 Encoding const *old_encoding = EncodingFind(mapping.bytes, mapping.n_bytes);
549
550 UnmapFile(&mapping);
551
552 HMODULE iconv_dll = LoadIconv();
553 if (iconv_dll == NULL)
554 return TCFieldStatusFileError;
555
556 status = IconvFile(filename, old_encoding, new_encoding);
557 if (status != TCFieldStatusSetSuccess) {
558 UnloadIconv(iconv_dll);
559 return TCFieldStatusFileError;
560 }
561
562 UnloadIconv(iconv_dll);
563
564 return TCFieldStatusSetSuccess;
565 }
566
567 /* Entry point into the plugin. */
568 BOOL APIENTRY
569 DllMain(HANDLE module, DWORD reason_for_call, LPVOID reserved)
570 {
571 UNREFERENCED_PARAMETER(module);
572 UNREFERENCED_PARAMETER(reserved);
573
574 return TRUE;
575 }
Something went wrong with that request. Please try again.