1 /*
2 CHMView - list and extract files from CHM/CHI HtmlHelp archives
3 Copyright (C) 2002-2012 Alex Yaroslavsky
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20 #ifdef UNIX
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #else
24 #include <windows.h>
25 #endif
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <limits.h>
31
32 #ifdef UNIX
33 #include <errno.h>
34 #include <locale.h>
35 #else
36 #include <dir.h>
37 #endif
38
39 #ifdef UNIX
40 #include <chm_lib.h>
41 #else
42 #include "chm_lib.h"
43 #endif
44
45 #include "utf8.h"
46
47 #ifdef UNIX
48 #define _strnicmp strncasecmp
49 #define MODE 0755
50 #endif
51
52 char *pointtoname(char *path);
53 void ReplaceIllegalChars(char *ptr);
54 void extract(struct chmFile *c, struct chmUnitInfo *ui,
55 long extractwithoutpath);
56 int _extract_callback_all(struct chmFile *c, struct chmUnitInfo *ui,
57 void *context);
58 int _extract_callback_dir(struct chmFile *c, struct chmUnitInfo *ui,
59 void *context);
60 int extractdir(struct chmFile *c, char *path, long extractwithoutpath);
61 int _print_ui(struct chmFile *c __unused, struct chmUnitInfo *ui,
62 void *context __unused);
63
64 struct cb_data
65 {
66 long extractwithoutpath;
67 char *path;
68 int pathlen;
69 };
70
71 static int error=0;
72
pointtoname(char * path)73 char *pointtoname(char *path)
74 {
75 char *p = path + strlen(path);
76 while (p > path && *(p-1) != '/')
77 --p;
78 return p;
79 }
80
81 #ifndef UNIX
makedir(char * path)82 int makedir(char *path)
83 {
84 char *end;
85 char *p;
86
87 p = path+3;
88 while ((end = strstr(p,"/")) != NULL)
89 {
90 long h;
91 struct _finddata_t f;
92 *end = '\0';
93 h = _findfirst(path,&f);
94 if (h!=-1)
95 {
96 _findclose(h);
97 *end = '/';
98 p = end + 1;
99 continue;
100 }
101
102 if (_mkdir(path)!=0)
103 {
104 printf(" Error: Couldn't make directory %s\n", path);
105 *end = '/';
106 return 0;
107 }
108 *end = '/';
109 p = end + 1;
110 }
111 return 1;
112 }
113 #else
makedir(char * path)114 static int makedir(char *path)
115 {
116 char *slash;
117 int was_error, done;
118
119 slash = path;
120 done = 0;
121 was_error = 0;
122
123 do {
124 slash = index(slash + 1, '/');
125 if (slash)
126 {
127 *slash = 0;
128 if (mkdir(path, MODE) == -1 && EEXIST != errno)
129 was_error = 1;
130 *slash = '/';
131 }
132 else
133 done = 1;
134 } while (! done && ! was_error);
135
136 if (was_error)
137 {
138 perror("mkdir");
139 exit(1);
140 /* return 0; */
141 }
142
143 return 1;
144 }
145 #endif
146
147 static const char ILLEGAL_SYMB[] = "<>:|?*\"";
148 static const char ILLEGAL_REPL[] = "()_!__'";
149
ReplaceIllegalChars(char * ptr)150 void ReplaceIllegalChars(char *ptr)
151 {
152 while ( *ptr )
153 {
154 const char *zz;
155 if ( (zz = strchr( ILLEGAL_SYMB, *ptr ) ) != NULL)
156 *ptr = ILLEGAL_REPL[zz-ILLEGAL_SYMB];
157
158 if ( *ptr < 32 || *ptr == 127 ) *ptr = 32;
159
160 ptr++;
161 }
162 }
163
savetofile(struct chmFile * c,struct chmUnitInfo * ui,long extractwithoutpath)164 static int savetofile(struct chmFile *c, struct chmUnitInfo *ui, long extractwithoutpath)
165 {
166 LONGINT64 length=0;
167 char *outbuf=NULL;
168 FILE *f;
169 char fullpath[CHM_MAX_PATHLEN*2+1];
170 char target[CHM_MAX_PATHLEN*2+1];
171 wchar_t temp[CHM_MAX_PATHLEN+1];
172 #ifdef UNIX
173 size_t pathlen;
174 #endif
175
176 if (ui->length)
177 {
178 outbuf = (char *)malloc((unsigned int)ui->length);
179 if (!outbuf)
180 return -1;
181 length = chm_retrieve_object(c, ui, outbuf, 0, ui->length);
182 if (length==0)
183 {
184 if (outbuf) free(outbuf);
185 return -1;
186 }
187 }
188 #ifndef UNIX
189 GetCurrentDirectory(sizeof(fullpath)-1,fullpath);
190 while ((p = strstr(fullpath,"\\")) != NULL)
191 *p = '/';
192 if (fullpath[strlen(fullpath)-1] != '/')
193 strcat(fullpath,"/");
194 #else
195 getcwd(fullpath, sizeof(fullpath)-1);
196 pathlen = strlen(fullpath);
197 if (pathlen == 0)
198 return -1; /* impossible but... defensive programming */
199 if (fullpath[pathlen - 1] != '/')
200 {
201 if (pathlen == PATH_MAX)
202 return -1;
203 else
204 fullpath[pathlen] = '/';
205 fullpath[pathlen+1] = 0;
206 }
207 #endif
208 decode_UTF8(temp,ui->path);
209 #ifndef UNIX
210 WideCharToMultiByte(CP_ACP,0,temp,-1,target,sizeof(target),NULL,NULL);
211 #else
212 wcstombs(target, temp, sizeof(target));
213 #endif
214 ReplaceIllegalChars(target);
215 strcat(fullpath,extractwithoutpath?pointtoname(target):(target[0]=='/'?target+1:target));
216 if (!extractwithoutpath)
217 if (!makedir(fullpath))
218 {
219 if (outbuf) free(outbuf);
220 return -1;
221 }
222 if (fullpath[strlen(fullpath)-1]!='/')
223 {
224 f = fopen(fullpath, "wb");
225 if (!f)
226 {
227 printf(" Error: Couldn't open %s\n", fullpath);
228 return -1;
229 }
230 if (length)
231 fwrite(outbuf, 1, length, f);
232 fclose(f);
233 }
234 if (outbuf) free(outbuf);
235 return 0;
236 }
237
extract(struct chmFile * c,struct chmUnitInfo * ui,long extractwithoutpath)238 void extract(struct chmFile *c, struct chmUnitInfo *ui, long extractwithoutpath)
239 {
240 char target[CHM_MAX_PATHLEN*2+1];
241 wchar_t temp[CHM_MAX_PATHLEN+1];
242
243 decode_UTF8(temp,ui->path);
244 #ifndef UNIX
245 WideCharToMultiByte(CP_OEMCP,0,temp,-1,target,sizeof(target),NULL,NULL);
246 #else
247 wcstombs(target, temp, sizeof(target));
248 #endif
249 printf("Extracting %s ",target);
250 if (savetofile(c, ui, extractwithoutpath))
251 {
252 printf("Error\n");
253 error=1;
254 //return CHM_ENUMERATOR_FAILURE;
255 } else
256 printf("OK\n");
257 }
258
_extract_callback_all(struct chmFile * c,struct chmUnitInfo * ui,void * context)259 int _extract_callback_all(struct chmFile *c,
260 struct chmUnitInfo *ui,
261 void *context)
262 {
263 extract(c,ui,(long)context);
264 return CHM_ENUMERATOR_CONTINUE;
265 }
266
_extract_callback_dir(struct chmFile * c,struct chmUnitInfo * ui,void * context)267 int _extract_callback_dir(struct chmFile *c,
268 struct chmUnitInfo *ui,
269 void *context)
270 {
271 struct cb_data *data = (struct cb_data *)context;
272 if (!_strnicmp(ui->path,data->path,data->pathlen))
273 {
274 extract(c,ui,data->extractwithoutpath);
275 }
276 return CHM_ENUMERATOR_CONTINUE;
277 }
278
extractdir(struct chmFile * c,char * path,long extractwithoutpath)279 int extractdir(struct chmFile *c, char *path, long extractwithoutpath)
280 {
281 struct cb_data data = {extractwithoutpath,path,strlen(path)};
282 chm_enumerate(c,CHM_ENUMERATE_ALL,_extract_callback_dir,(void *)&data);
283 return 0;
284 }
285
_print_ui(struct chmFile * c __unused,struct chmUnitInfo * ui,void * context __unused)286 int _print_ui(struct chmFile *c __unused,
287 struct chmUnitInfo *ui,
288 void *context __unused)
289 {
290 char target[CHM_MAX_PATHLEN*2+1];
291 wchar_t temp[CHM_MAX_PATHLEN+1];
292
293 decode_UTF8(temp,ui->path);
294 #ifndef UNIX
295 WideCharToMultiByte(CP_OEMCP,0,temp,-1,target,sizeof(target),NULL,NULL);
296 #else
297 wcstombs(target, temp, sizeof(target));
298 #endif
299
300 if ((target)[0] == '/')
301 {
302 printf("%12.12llu ",ui->length);
303 printf("%s\n",target);
304 }
305 else
306 {
307 printf("%12.12llu ",ui->length);
308 printf("/");
309 printf("%s\n",target);
310 }
311 return CHM_ENUMERATOR_CONTINUE;
312 }
313
usage(void)314 static void usage(void)
315 {
316 printf("CHMView v2.0 beta 4 with UTF-8 support (February 19 2012)\n");
317 printf("(c) Alex Yaroslavsky at yandex / trexinc.\n");
318 printf("Using:\nCHMLib library by Jed Wing at ugcs.caltech.edu / jedwin\n");
319 printf("LZX library by Stuart Caie at 4u.net / kyzer\n");
320 printf("UTF8 en/decoder by Oleg Bondar at mail.ru / hobo-mts\n\n");
321 printf("Usage: chmview <option> chmfile [/file_to_extract or @file_with_list]\n");
322 printf(" Options:\n");
323 printf(" l - list archive contents\n");
324 printf(" e - extract without path names\n");
325 printf(" x - extract\n");
326 printf(" Notes:\n");
327 printf(" When extracting specific files, file name MUST start with `/'.\n");
328 printf(" No file masks should be used.\n");
329 }
330
main(int argc,char * argv[])331 int main(int argc, char *argv[])
332 {
333 struct chmFile *c;
334 char *infname;
335 char *command;
336 struct chmUnitInfo ui;
337
338 if (argc < 3)
339 {
340 usage();
341 exit(-1);
342 }
343
344 infname = argv[2];
345
346 #ifdef UNIX
347 setlocale(LC_ALL,"");
348 #endif
349 c = chm_open(infname);
350 if (!c)
351 exit(-1);
352
353 command = argv[1];
354
355 switch (command[0])
356 {
357 case 'l':
358 {
359 printf("--------\n");
360 chm_enumerate(c,CHM_ENUMERATE_ALL,_print_ui,NULL);
361 printf("--------\n");
362 break;
363 }
364
365 case 'e':
366 case 'x':
367 {
368 char target[CHM_MAX_PATHLEN*2+1];
369 wchar_t temp[CHM_MAX_PATHLEN+1];
370 int status;
371 long extractwithoutpath = command[0]=='e'?1:0;
372
373 if (argc == 4)
374 {
375 char *p;
376 int s;
377 char name[CHM_MAX_PATHLEN*2+1];
378 FILE *f;
379 strcpy(name,argv[3]);
380 f=NULL;
381 if (name[0]=='@')
382 {
383 f = fopen(&name[1],"rt");
384 if (f)
385 {
386 if (fgets(name,sizeof(name),f))
387 {
388 if (name[strlen(name)-1]=='\n')
389 name[strlen(name)-1]=0;
390 if (name[strlen(name)-1]=='\r')
391 name[strlen(name)-1]=0;
392 }
393 else
394 name[0]=0;
395 }
396 else
397 error=1;
398 }
399 while (name[0]!=0)
400 {
401 while ((p = strstr(name,"\\")) != NULL)
402 *p = '/';
403 if (name[0]!='/' && name[0]!=':')
404 strcpy(target,"/");
405 else
406 *target=0;
407 if (name[0]=='/' && name[1]==':' && name[2]==':')
408 strcat(target,name+1);
409 else
410 strcat(target,name);
411 #ifndef UNIX
412 MultiByteToWideChar(CP_ACP,0,target,-1,temp,sizeof(temp));
413 #else
414 mbstowcs(temp, target, sizeof(temp));
415 #endif
416 encode_UTF8(target,temp);
417 status = chm_resolve_object(c,target,&ui);
418 if (status==CHM_RESOLVE_SUCCESS && ui.path[strlen(ui.path)-1]!='/')
419 {
420 s=savetofile(c,&ui,extractwithoutpath);
421 if (s==-1)
422 error=1;
423 }
424 else
425 {
426 if (extractdir(c,target,extractwithoutpath))
427 error=1;
428 }
429 if (f)
430 {
431 if (fgets(name,sizeof(name),f))
432 {
433 if (name[strlen(name)-1]=='\n')
434 name[strlen(name)-1]=0;
435 if (name[strlen(name)-1]=='\r')
436 name[strlen(name)-1]=0;
437 }
438 else
439 name[0]=0;
440 }
441 else
442 break;
443 }
444 if (f)
445 fclose(f);
446 }
447 else
448 {
449 chm_enumerate(c,CHM_ENUMERATE_ALL,_extract_callback_all,(void *)extractwithoutpath);
450 }
451 break;
452 }
453 }
454
455 chm_close(c);
456
457 return (error?-1:0);
458 }
459