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