1/*
2 * Copyright 2011 Aleksas Pantechovskis, <alexp.frl@gmail.com>
3 * Copyright 2011 Siarzhuk Zharski, <imker@gmx.li>
4 * All rights reserved. Distributed under the terms of the MIT License.
5 */
6
7#include <iomanip>
8#include <iostream>
9#include <map>
10#include <math.h>
11#include <set>
12#include <sstream>
13#include <stdio.h>
14#include <stdlib.h>
15#include <strings.h>
16#include <vector>
17
18#include <Application.h>
19#include <Bitmap.h>
20#include <Entry.h>
21#include <InterfaceDefs.h>
22#include <Message.h>
23#include <Mime.h>
24#include <Path.h>
25#include <String.h>
26
27
28using namespace std;
29
30
31const char* kUsageMessage = "# setmime:\n"
32	"# usage: setmime ((-dump | -dumpSniffRule | -dumpIcon | -dumpAll) "
33													"[ <signatureString> ] )\n"
34	"#      | (-remove <signatureString> )\n"
35	"#      | ( (-set | -force | -add)  <signatureString>\n"
36	"#          [ -short <short description> ] [ -long <long description> ]\n"
37	"#          [ -preferredApp <preferred app path> ]\n"
38	"#          [ -preferredAppSig <preferred app signature> ]\n"
39	"#          [ -sniffRule <sniffRule> ]\n"
40	"#          [ -extension <file suffix> ]\n"
41	"#          [ -attribute <internal name>\n"
42	"#             [ -attrName <public name> ] [ -attrType <type code> ]\n"
43	"#             [ -attrWidth <display width> ][ -attrAlignment <position>]\n"
44	"#             [ -attrViewable <bool flag> ][ -attrEditable <bool flag> ]\n"
45	"#             [ -attrExtra <bool flag> ] ]\n"
46	"#          [ -miniIcon <256 hex bytes> ]\n"
47	"#          [ -largeIcon <1024 hex bytes> ]\n"
48	"#          [ -vectorIcon <icon hex bytes> ] ... )\n"
49	"#      | (-checkSniffRule <sniffRule>\n"
50	"#      | -includeApps)\n";
51
52const char* kHelpMessage = "#  -dump prints a specified metamime\n"
53	"#  -remove removes specified metamime\n"
54	"#  -add adds specified metamime and specified metamime attributes\n"
55	"#      that have not yet been defined\n"
56	"#  -set adds specified metamime and specified metamime attributes, \n"
57	"#      overwrites the existing values of specified metamime attributes\n"
58	"#  -force adds specified metamime and specified metamime attributes\n"
59	"#      after first erasing all the existing attributes\n"
60	"#  -dumpSniffRule prints just the MIME sniffer rule of a "
61														"specified metamime\n"
62	"#     -dumpIcon prints just the icon information of a specified metamime\n"
63	"#  -dumpAll prints all the information, including icons of a "
64														"specified metamime\n"
65	"#  -checkSniffRule parses a MIME sniffer rule and reports any errors\n"
66	"#  -includeApps will include applications\n";
67
68const char* kNeedArgMessage =	"you have to specify any of "
69								"-dump[All|Icon|SnifferRule], -add, -set, "
70								"-force or -remove";
71
72const char* kWrongModeMessage = "can only specify one of -dump, -dumpAll, "
73								"-dumpIcon, -dumpSnifferRule, -remove, "
74								"-add, -set, -force or -checkSnifferRule";
75
76const char* kHelpReq		=	"--help";
77const char* kDump			=	"-dump";
78const char* kDumpSniffRule	=	"-dumpSniffRule";
79const char* kDumpIcon		=	"-dumpIcon";
80const char* kDumpAll		=	"-dumpAll";
81const char* kAdd			=	"-add";
82const char* kSet			=	"-set";
83const char* kForce			=	"-force";
84const char* kRemove			=	"-remove";
85const char* kCheckSniffRule	=	"-checkSniffRule";
86const char* kShort			=	"-short";
87const char* kLong			=	"-long";
88const char* kPreferredApp	=	"-preferredApp";
89const char* kPreferredAppSig =	"-preferredAppSig";
90const char* kSniffRule		=	"-sniffRule";
91const char* kMiniIcon		=	"-miniIcon";
92const char* kLargeIcon		=	"-largeIcon";
93const char* kVectorIcon		=	"-vectorIcon";
94const char* kIncludeApps	=	"-includeApps";
95const char* kExtension		=	"-extension";
96const char* kAttribute		=	"-attribute";
97const char* kAttrName		=	"-attrName";
98const char* kAttrType		=	"-attrType";
99const char* kAttrWidth		=	"-attrWidth";
100const char* kAttrAlignment	=	"-attrAlignment";
101const char* kAttrViewable	=	"-attrViewable";
102const char* kAttrEditable	=	"-attrEditable";
103const char* kAttrExtra		=	"-attrExtra";
104
105
106const uint32 hash_function(const char* str)
107{
108	uint32 h = 0;
109	uint32 g = 0;
110	for (const char* p = str; *p; p++) {
111		h = (h << 4) + (*p & 0xFF);
112		g = h & 0xF0000000;
113		if (g != 0) {
114			h ^= g >> 24;
115			h ^= g;
116		}
117	}
118	return h;
119}
120
121
122// the list of all acceptable command-line options
123struct CmdOption {
124
125	enum Type {
126		kMode,
127		kOption,
128		kAttrRoot,
129		kAttrib,
130		kHelp
131	};
132
133	const char*	fName;
134	Type		fType;
135	bool		fNeedArg;
136	bool		fNonExclusive;
137
138} gCmdOptions[] = {
139
140	{ kHelpReq,			CmdOption::kHelp },
141
142	{ kDump,			CmdOption::kMode },
143	{ kDumpSniffRule,	CmdOption::kMode },
144	{ kDumpIcon,		CmdOption::kMode },
145	{ kDumpAll,			CmdOption::kMode },
146	{ kAdd,				CmdOption::kMode },
147	{ kSet,				CmdOption::kMode },
148	{ kForce,			CmdOption::kMode },
149	{ kRemove,			CmdOption::kMode },
150	{ kCheckSniffRule,	CmdOption::kMode, true },
151
152	{ kShort,			CmdOption::kOption, true },
153	{ kLong,			CmdOption::kOption, true },
154	{ kPreferredApp,	CmdOption::kOption, true },
155	{ kPreferredAppSig,	CmdOption::kOption, true },
156	{ kSniffRule,		CmdOption::kOption, true },
157	{ kMiniIcon	,		CmdOption::kOption, true },
158	{ kLargeIcon,		CmdOption::kOption, true },
159	{ kVectorIcon,		CmdOption::kOption, true },
160	{ kIncludeApps,		CmdOption::kOption, false },
161	{ kExtension,		CmdOption::kOption, true, true },
162	{ kAttribute,		CmdOption::kAttrRoot, true, true },
163
164	{ kAttrName,		CmdOption::kAttrib, true },
165	{ kAttrType,		CmdOption::kAttrib, true },
166	{ kAttrWidth,		CmdOption::kAttrib, true },
167	{ kAttrAlignment,	CmdOption::kAttrib, true },
168	{ kAttrViewable,	CmdOption::kAttrib, true },
169	{ kAttrEditable,	CmdOption::kAttrib, true },
170	{ kAttrExtra,		CmdOption::kAttrib, true }
171};
172
173// the 'hash -> value' map of arguments provided by user
174typedef multimap<uint32, const char*>				TUserArgs;
175typedef multimap<uint32, const char*>::iterator		TUserArgsI;
176
177// user provided attributes are grouped separately in vector
178typedef vector<TUserArgs>			TUserAttrs;
179typedef vector<TUserArgs>::iterator	TUserAttrsI;
180
181const uint32 kOpModeUndefined = 0;
182
183
184// #pragma mark -
185
186class Error : public std::exception
187{
188			BString		fWhat;
189public:
190						Error(const char* what, ...);
191	virtual				~Error() throw() {}
192	virtual const char*	what() const throw() { return fWhat.String(); }
193};
194
195
196Error::Error(const char* what, ...)
197{
198	const int size = 1024;
199	va_list args;
200	va_start(args, what);
201	vsnprintf(fWhat.LockBuffer(size), size, what, args);
202	fWhat.UnlockBuffer();
203	va_end(args);
204}
205
206
207// #pragma mark -
208
209// encapsulate the single attribute params
210//
211struct MimeAttribute
212{
213	status_t	fStatus;
214	BString 	fName;
215	BString 	fPublicName;
216	int32 		fType;
217	bool 		fViewable;
218	bool 		fEditable;
219	bool 		fExtra;
220	int32 		fWidth;
221	int32 		fAlignment;
222
223				MimeAttribute(BMessage& msg, int32 index);
224				MimeAttribute(TUserArgs& args);
225				MimeAttribute(const MimeAttribute& src);
226
227	status_t	InitCheck() { return fStatus; }
228
229	MimeAttribute& operator=(const MimeAttribute& src);
230
231	void		Dump();
232	void		SyncWith(TUserArgs& args);
233	void		StoreInto(BMessage* target);
234	const char*	UserArgValue(TUserArgs& map, const char* name);
235
236	bool		IsPrintableChar(char c)
237					{ return c >= ' ' && c < 127 && c != '\'' && c != '\\'; }
238};
239
240
241MimeAttribute::MimeAttribute(BMessage& msg, int32 index)
242		:
243		fStatus(B_NO_INIT),
244		fType('CSTR'),
245		fViewable(true),
246		fEditable(false),
247		fExtra(false),
248		fWidth(0),
249		fAlignment(0)
250{
251	BString rawPublicName;
252	struct attrEntry {
253		const char* name;
254		type_code	type;
255		bool		required;
256		void*		data;
257	} attrEntries[] = {
258		{ "attr:name",			B_STRING_TYPE,	true, &fName },
259		{ "attr:public_name",	B_STRING_TYPE,	true, &rawPublicName },
260		{ "attr:type",			B_INT32_TYPE,	true, &fType },
261		{ "attr:viewable",		B_BOOL_TYPE,	false, &fViewable },
262		{ "attr:editable",		B_BOOL_TYPE,	false, &fEditable },
263		{ "attr:extra",			B_BOOL_TYPE,	false, &fExtra },
264		{ "attr:width",			B_INT32_TYPE,	false, &fWidth },
265		{ "attr:alignment",		B_INT32_TYPE,	false, &fAlignment }
266	};
267
268	for (size_t i = 0; i < sizeof(attrEntries) / sizeof(attrEntries[0]); i++) {
269		switch (attrEntries[i].type) {
270			case B_STRING_TYPE:
271				fStatus = msg.FindString(attrEntries[i].name, index,
272								(BString*)attrEntries[i].data);
273				break;
274			case B_BOOL_TYPE:
275				fStatus = msg.FindBool(attrEntries[i].name, index,
276								(bool*)attrEntries[i].data);
277				break;
278			case B_INT32_TYPE:
279				fStatus = msg.FindInt32(attrEntries[i].name, index,
280								(int32*)attrEntries[i].data);
281				break;
282		}
283
284		if (attrEntries[i].required && fStatus != B_OK)
285			return;
286	}
287
288	fPublicName.CharacterEscape(rawPublicName, "\'", '\\');
289	fStatus = B_OK;
290}
291
292
293MimeAttribute::MimeAttribute(TUserArgs& args)
294		:
295		fStatus(B_NO_INIT),
296		fType('CSTR'),
297		fViewable(true),
298		fEditable(false),
299		fExtra(false),
300		fWidth(0),
301		fAlignment(0)
302{
303	SyncWith(args);
304	fStatus = B_OK;
305}
306
307
308MimeAttribute::MimeAttribute(const MimeAttribute& src)
309{
310	*this = src;
311}
312
313
314MimeAttribute&
315MimeAttribute::operator=(const MimeAttribute& src)
316{
317	fStatus = src.fStatus;
318	fName = src.fName;
319	fPublicName = src.fPublicName;
320	fType = src.fType;
321	fViewable = src.fViewable;
322	fEditable = src.fEditable;
323	fExtra = src.fExtra;
324	fWidth = src.fWidth;
325	fAlignment = src.fAlignment;
326
327	return *this;
328}
329
330
331void
332MimeAttribute::SyncWith(TUserArgs& args)
333{
334	const char* value = UserArgValue(args, kAttribute);
335	if (value != NULL)
336		fName.SetTo(value, B_MIME_TYPE_LENGTH);
337
338	value = UserArgValue(args, kAttrName);
339	if (value != NULL)
340		fPublicName.SetTo(value, B_MIME_TYPE_LENGTH);
341
342	value = UserArgValue(args, kAttrType);
343	if (value != NULL) {
344		fType = 0;
345		if (strlen(value) > 2 && value[0] == '0' && value[1] == 'x') {
346			stringstream ss;
347			ss << setbase(16) << value + 2;
348			ss >> fType;
349		} else if (strlen(value) == 4) {
350			for (int i = 0; i < 4 && value[i] != '\0'; i++) {
351				fType <<= 8;
352				fType |= (value[i] != '\0' ? value[i] : ' ');
353			}
354
355		} else
356			throw Error("Invalid data for %s", kAttrType);
357
358		fType = B_LENDIAN_TO_HOST_INT32(fType);
359	}
360
361	value = UserArgValue(args, kAttrWidth);
362	if (value != NULL)
363		fWidth = atoi(value);
364
365	value = UserArgValue(args, kAttrAlignment);
366	if (value != NULL) {
367		if (strcasecmp(value, "right") == 0) {
368			fAlignment = B_ALIGN_RIGHT;
369		} else if (strcasecmp(value, "left") == 0) {
370			fAlignment = B_ALIGN_LEFT;
371		} else if (strcasecmp(value, "center") == 0) {
372			fAlignment = B_ALIGN_CENTER;
373		} else
374			fAlignment = atoi(value);
375	}
376
377	value = UserArgValue(args, kAttrViewable);
378	if (value != NULL)
379		fViewable = atoi(value) != 0;
380
381	value = UserArgValue(args, kAttrEditable);
382	if (value != NULL)
383		fEditable = atoi(value) != 0;
384
385	value = UserArgValue(args, kAttrExtra);
386	if (value != NULL)
387		fExtra = atoi(value) != 0;
388}
389
390
391void
392MimeAttribute::Dump()
393{
394	uint32 type = B_HOST_TO_LENDIAN_INT32(fType);
395	const char* alignment = fAlignment == B_ALIGN_RIGHT ? "right"
396					: (fAlignment == B_ALIGN_LEFT ? "left" : "center");
397
398	cout << " \\" << endl << "\t" << kAttribute << " \"" << fName << "\" "
399				<< kAttrName << " \"" << fPublicName << "\"";
400
401	char c1 = (char)((type >> 24) & 0xFF);
402	char c2 = (char)((type >> 16) & 0xFF);
403	char c3 = (char)((type >> 8) & 0xFF);
404	char c4 = (char)(type & 0xFF);
405
406	ios::fmtflags flags = cout.flags();
407
408	cout << " \\" << endl << "\t\t" << kAttrType;
409	if (IsPrintableChar(c1) && IsPrintableChar(c2) &&
410		IsPrintableChar(c3) && IsPrintableChar(c4))
411		cout << " '" << c1 << c2 << c3 << c4 << "' ";
412	else
413		cout << "0x" << hex << type;
414
415	cout << " " << kAttrWidth << " " << fWidth
416			<< " " << kAttrAlignment << " " << alignment;
417
418	cout << " \\" << endl << "\t\t" << kAttrViewable << " " << fViewable
419			<< " " << kAttrEditable << " " << fEditable
420			<< " " << kAttrExtra << " " << fExtra;
421
422	cout.flags(flags);
423}
424
425
426void
427MimeAttribute::StoreInto(BMessage* target)
428{
429	struct attrEntry {
430		const char* name;
431		type_code	type;
432		const void*	data;
433	} attrEntries[] = {
434		{ "attr:name",			B_STRING_TYPE,	fName.String() },
435		{ "attr:public_name",	B_STRING_TYPE,	fPublicName.String() },
436		{ "attr:type",			B_INT32_TYPE,	&fType },
437		{ "attr:viewable",		B_BOOL_TYPE,	&fViewable },
438		{ "attr:editable",		B_BOOL_TYPE,	&fEditable },
439		{ "attr:extra",			B_BOOL_TYPE,	&fExtra },
440		{ "attr:width",			B_INT32_TYPE,	&fWidth },
441		{ "attr:alignment",		B_INT32_TYPE,	&fAlignment }
442	};
443
444	for (size_t i = 0; i < sizeof(attrEntries) / sizeof(attrEntries[0]); i++) {
445		switch (attrEntries[i].type) {
446			case B_STRING_TYPE:
447				fStatus = target->AddString(attrEntries[i].name,
448								(const char*)attrEntries[i].data);
449				break;
450			case B_BOOL_TYPE:
451				fStatus = target->AddBool(attrEntries[i].name,
452								(bool*)attrEntries[i].data);
453				break;
454			case B_INT32_TYPE:
455				fStatus = target->AddInt32(attrEntries[i].name,
456								*(int32*)attrEntries[i].data);
457				break;
458		}
459
460		if (fStatus != B_OK)
461			return;
462	}
463}
464
465
466const char*
467MimeAttribute::UserArgValue(TUserArgs& map, const char* name)
468{
469	TUserArgsI i = map.find(hash_function(name));
470	if (i == map.end())
471		return NULL;
472	return i->second != NULL ? i->second : "";
473}
474
475
476// #pragma mark -
477
478// the work-horse of the app - the class encapsulates extended info readed
479// from the mime type and do all edit and dump operations
480//
481class MimeType : public BMimeType {
482
483public:
484					MimeType(char** argv);
485					~MimeType();
486
487	void			Process();
488
489private:
490	status_t		_InitCheck();
491	void			_SetTo(const char* mimetype);
492	void			_PurgeProperties();
493	void			_Init(char** argv);
494	void			_DumpIcon(uint8 *iconData, size_t iconSize);
495	void			_Dump(const char* mimetype);
496	void			_DoEdit();
497	void			_SetIcon(const char* iconData, int32 iconSize);
498
499	const char*		_UserArgValue(const char* name);
500
501	status_t		fStatus;
502	const char*		fToolName;
503
504	// configurable MimeType properties
505	BString			fShort;
506	BString			fLong;
507	BString			fPrefApp;
508	BString			fPrefAppSig;
509	BString			fSniffRule;
510	BBitmap*		fSmallIcon;
511	BBitmap*		fBigIcon;
512	uint8*			fVectorIcon;
513	size_t			fVectorIconSize;
514
515	map<uint32, BString>		fExtensions;
516	map<uint32, MimeAttribute>	fAttributes;
517
518	// user provided arguments
519	TUserArgs		fUserArguments;
520	TUserAttrs		fUserAttributes;
521
522	// operation mode switches and flags
523	uint32			fOpMode;
524	bool			fDumpNormal;
525	bool			fDumpRule;
526	bool			fDumpIcon;
527	bool			fDumpAll;
528	bool			fDoAdd;
529	bool			fDoSet;
530	bool			fDoForce;
531	bool			fDoRemove;
532	bool			fCheckSniffRule;
533};
534
535
536MimeType::MimeType(char** argv)
537		:
538		fStatus(B_NO_INIT),
539		fToolName(argv[0]),
540		fSmallIcon(NULL),
541		fBigIcon(NULL),
542		fVectorIcon(NULL),
543		fVectorIconSize(0),
544		fOpMode(kOpModeUndefined),
545		fDumpNormal(false),
546		fDumpRule(false),
547		fDumpIcon(false),
548		fDumpAll(false),
549		fDoAdd(false),
550		fDoSet(false),
551		fDoForce(false),
552		fDoRemove(false),
553		fCheckSniffRule(false)
554{
555	fToolName = strrchr(argv[0], '/');
556	fToolName = fToolName == NULL ? argv[0] : fToolName + 1;
557
558	_Init(++argv);
559}
560
561
562MimeType::~MimeType()
563{
564	delete fSmallIcon;
565	delete fBigIcon;
566	free(fVectorIcon);
567}
568
569
570void
571MimeType::_Init(char** argv)
572{
573	// fill the helper map of options - for quick lookup of arguments
574	map<uint32, const CmdOption*> cmdOptionsMap;
575	for (size_t i = 0; i < sizeof(gCmdOptions) / sizeof(gCmdOptions[0]); i++)
576		cmdOptionsMap.insert(pair<uint32, CmdOption*>(
577							hash_function(gCmdOptions[i].fName), &gCmdOptions[i]));
578
579	// parse the command line arguments
580	for (char** arg = argv; *arg; arg++) {
581		// non-option arguments are assumed as signature
582		if (**arg != '-') {
583			if (Type() != NULL)
584				throw Error("mime signature already specified: '%s'", Type());
585
586			SetTo(*arg);
587			continue;
588		}
589
590		// check op.modes, options and attribs
591		uint32 key = hash_function(*arg);
592
593		map<uint32, const CmdOption*>::iterator I = cmdOptionsMap.find(key);
594		if (I == cmdOptionsMap.end())
595			throw Error("unknown option '%s'", *arg);
596
597		switch (I->second->fType) {
598			case CmdOption::kHelp:
599				cerr << kUsageMessage;
600				throw Error(kHelpMessage);
601
602			case CmdOption::kMode:
603				// op.modes are exclusive - no simultaneous possible
604				if (fOpMode != kOpModeUndefined)
605					throw Error(kWrongModeMessage);
606				fOpMode = key;
607
608				if (hash_function(I->second->fName) != hash_function(kCheckSniffRule))
609					break;
610				// else -> fallthrough, CheckRule works both as mode and Option
611			case CmdOption::kOption:
612				{
613					const char* name = *arg;
614					const char* param = NULL;
615					if (I->second->fNeedArg) {
616						if (!*++arg)
617							throw Error("argument required for '%s'", name);
618						param = *arg;
619					}
620
621					TUserArgsI A = fUserArguments.find(key);
622					if (A != fUserArguments.end() && !I->second->fNonExclusive)
623						throw Error("option '%s' already specified", name);
624
625					fUserArguments.insert(
626							pair<uint32, const char*>(key, param));
627				}
628				break;
629
630			case CmdOption::kAttrRoot:
631				if (!*++arg)
632					throw Error("attribute name should be specified");
633
634				fUserAttributes.resize(fUserAttributes.size() + 1);
635				fUserAttributes.back().insert(
636							pair<uint32, const char*>(key, *arg));
637				break;
638
639			case CmdOption::kAttrib:
640				{
641					const char* name = *arg;
642					if (fUserAttributes.size() <= 0)
643						throw Error("'%s' allowed only after the '%s' <name>",
644								name, kAttribute);
645
646					if (!*++arg || **arg == '-')
647						throw Error("'%s', argument should be specified", name);
648
649					TUserArgsI A = fUserAttributes.back().find(key);
650					if (A != fUserAttributes.back().end())
651						throw Error("'%s' for attribute '%s' already specified",
652								name, A->second);
653
654					fUserAttributes.back().insert(
655							pair<uint32, const char*>(key, *arg));
656				}
657				break;
658
659			default:
660				throw Error("internal error. wrong mode: %d", I->second->fType);
661		}
662	}
663
664	// check some mutual exclusive conditions
665	if (fOpMode == kOpModeUndefined)
666		throw Error(kNeedArgMessage);
667
668	if (Type() != NULL && InitCheck() != B_OK)
669		throw Error("error instantiating mime for '%s': %s",
670							Type(), strerror(InitCheck()));
671
672	fDoAdd = fOpMode == hash_function(kAdd);
673	fDoSet = fOpMode == hash_function(kSet);
674	fDoForce = fOpMode == hash_function(kForce);
675	fDoRemove = fOpMode == hash_function(kRemove);
676	fDumpNormal = fOpMode == hash_function(kDump);
677	fDumpRule = fOpMode == hash_function(kDumpSniffRule);
678	fDumpIcon = fOpMode == hash_function(kDumpIcon);
679	fDumpAll = fOpMode == hash_function(kDumpAll);
680	fCheckSniffRule = fOpMode == hash_function(kCheckSniffRule);
681
682	if (fDoAdd || fDoSet || fDoForce || fDoRemove) {
683		if (Type() == NULL)
684			throw Error("signature should be specified");
685
686		if (!IsValid())
687			throw Error("mime for '%s' is not valid", Type());
688
689	} else if (fDumpNormal || fDumpRule || fDumpIcon || fDumpAll) {
690		if (Type() != NULL) {
691			if (!IsValid())
692				throw Error("mime for '%s' is not valid", Type());
693
694			if (!IsInstalled())
695				throw Error("mime for '%s' is not installed", Type());
696		}
697	}
698
699	// finally force to load mime-specific fileds
700	_SetTo(Type());
701}
702
703
704status_t
705MimeType::_InitCheck()
706{
707	return fStatus != B_OK ? fStatus : BMimeType::InitCheck();
708}
709
710
711void
712MimeType::_PurgeProperties()
713{
714	fShort.Truncate(0);
715	fLong.Truncate(0);
716	fPrefApp.Truncate(0);
717	fPrefAppSig.Truncate(0);
718	fSniffRule.Truncate(0);
719
720	delete fSmallIcon;
721	fSmallIcon = NULL;
722
723	delete fBigIcon;
724	fBigIcon = NULL;
725
726	free(fVectorIcon);
727	fVectorIcon = NULL;
728
729	fExtensions.clear();
730	fAttributes.clear();
731}
732
733
734void
735MimeType::_DumpIcon(uint8 *iconData, size_t iconSize)
736{
737	// bitmap icons ASCII art :)
738	int lineLimit = iconSize == B_MINI_ICON * B_MINI_ICON
739						? B_MINI_ICON : B_LARGE_ICON;
740
741	ios::fmtflags flags = cout.flags();
742
743	for (size_t i = 0; i < iconSize; i++) {
744		if (i % lineLimit == 0 && i != iconSize - 1)
745			cout << "\\" << endl;
746
747		cout << hex << setfill('0') << setw(2) << (uint16) iconData[i];
748	}
749
750	cout.flags(flags);
751}
752
753
754void
755MimeType::_SetIcon(const char* iconData, int32 iconSize)
756{
757	uint8* bits = NULL;
758	BRect rect(0, 0, iconSize - 1, iconSize - 1);
759
760	switch (iconSize) {
761		case B_MINI_ICON:
762			if (fSmallIcon == NULL)
763				fSmallIcon = new BBitmap(rect, B_COLOR_8_BIT);
764			bits = (uint8*) fSmallIcon->Bits();
765			break;
766		case B_LARGE_ICON:
767			if (fBigIcon == NULL)
768				fBigIcon = new BBitmap(rect, B_COLOR_8_BIT);
769			bits = (uint8*) fBigIcon->Bits();
770			break;
771		default:
772			if (iconSize >= 0)
773				break;
774			free(fVectorIcon);
775			fVectorIconSize = -iconSize;
776			bits = fVectorIcon = (uint8*) malloc(fVectorIconSize);
777			break;
778	}
779
780	if (bits == NULL)
781		throw Error("cannot create icon of size %d", iconSize);
782
783	size_t dataSize = iconSize < 0 ? -iconSize / 2 : iconSize * iconSize;
784
785	for (size_t i = 0; i < dataSize; i++) {
786		stringstream ss;
787		uint16 val;
788		ss << setbase(16) << iconData[i * 2] << iconData[i * 2 + 1];
789		ss >> val;
790		bits[i] = uint8(val & 0xff);
791	}
792
793	if (iconSize < 0)
794		SetIcon(fVectorIcon, dataSize);
795	else
796		SetIcon(iconSize == B_MINI_ICON ? fSmallIcon : fBigIcon,
797					(icon_size) iconSize);
798}
799
800
801void
802MimeType::_SetTo(const char* mimetype)
803{
804	if (mimetype == NULL)
805		return; // iterate all types - nothing to load ATM
806
807	if (BMimeType::SetTo(mimetype) != B_OK)
808		throw Error("failed to set mimetype to '%s'", mimetype);
809
810	_PurgeProperties();
811
812	char buffer[B_MIME_TYPE_LENGTH] = { 0 };
813	if (GetShortDescription(buffer) == B_OK)
814		fShort.SetTo(buffer, B_MIME_TYPE_LENGTH);
815
816	if (GetLongDescription(buffer) == B_OK)
817		fLong.SetTo(buffer, B_MIME_TYPE_LENGTH);
818
819	entry_ref ref;
820	if (GetAppHint(&ref) == B_OK) {
821		BPath path(&ref);
822		fPrefApp.SetTo(path.Path(), B_MIME_TYPE_LENGTH);
823	}
824
825	if (GetPreferredApp(buffer, B_OPEN) == B_OK)
826		fPrefAppSig.SetTo(buffer, B_MIME_TYPE_LENGTH);
827
828	BString rule;
829	if (GetSnifferRule(&rule) == B_OK)
830		fSniffRule.CharacterEscape(rule.String(), "\'", '\\');
831
832	BMessage exts;
833	fExtensions.clear();
834	if (GetFileExtensions(&exts) == B_OK) {
835		uint32 i = 0;
836		const char* ext = NULL;
837		while (exts.FindString("extensions", i++, &ext) == B_OK)
838			fExtensions.insert(pair<uint32, BString>(hash_function(ext), ext));
839	}
840
841	BMessage attrs;
842	fAttributes.clear();
843	if (GetAttrInfo(&attrs) == B_OK) {
844		for (int index = 0; ; index++) {
845			MimeAttribute attr(attrs, index);
846			if (attr.InitCheck() != B_OK)
847				break;
848
849			fAttributes.insert(
850					pair<uint32, MimeAttribute>(hash_function(attr.fName), attr));
851		}
852	}
853
854	fSmallIcon = new BBitmap(BRect(0, 0, 15, 15), B_COLOR_8_BIT);
855	if (GetIcon(fSmallIcon, B_MINI_ICON) != B_OK) {
856		delete fSmallIcon;
857		fSmallIcon = NULL;
858	}
859
860	fBigIcon = new BBitmap(BRect(0, 0, 31, 31), B_COLOR_8_BIT);
861	if (GetIcon(fBigIcon, B_LARGE_ICON) != B_OK) {
862		delete fBigIcon;
863		fBigIcon = NULL;
864	}
865
866	if (GetIcon(&fVectorIcon, &fVectorIconSize) != B_OK)
867		fVectorIcon = NULL;
868}
869
870
871const char*
872MimeType::_UserArgValue(const char* name)
873{
874	TUserArgsI i = fUserArguments.find(hash_function(name));
875	if (i == fUserArguments.end())
876		return NULL;
877
878	return i->second != NULL ? i->second : "";
879}
880
881
882void
883MimeType::_Dump(const char* mimetype)
884{
885	// _Dump can be called as part of all types iteration - so set to required
886	if (Type() == NULL || strcasecmp(Type(), mimetype) != 0)
887		_SetTo(mimetype);
888
889	// apps have themself as preferred app - use it to handle
890	// -includeApps option - do not dump applications info
891	if (!fPrefApp.IsEmpty()
892		&& fPrefApp.ICompare(mimetype) == 0
893		&& _UserArgValue(kIncludeApps) == NULL)
894			return;
895
896	if (fDumpIcon && fSmallIcon == NULL && fBigIcon == NULL)
897		return;
898
899	if (fDumpRule && fSniffRule.IsEmpty())
900		return;
901
902	cout << fToolName << " -set " << mimetype;
903
904	if (fDumpNormal || fDumpAll) {
905		if (!fShort.IsEmpty())
906			cout << " " << kShort << " \"" << fShort << "\"";
907		if (!fLong.IsEmpty())
908			cout << " " << kLong << " \"" << fLong << "\"";
909		if (!fPrefApp.IsEmpty())
910			cout << " " << kPreferredApp << " " << fPrefApp;
911		if (!fPrefAppSig.IsEmpty())
912			cout << " " << kPreferredAppSig << " " << fPrefAppSig;
913	}
914
915	if (!fDumpIcon && !fSniffRule.IsEmpty())
916		cout << " " << kSniffRule << " '" << fSniffRule << "'";
917
918	if (fDumpNormal || fDumpAll)
919		for (map<uint32, BString>::iterator i = fExtensions.begin();
920				i != fExtensions.end(); i++)
921			cout << " " << kExtension << " " << i->second;
922
923	if (fDumpAll)
924		for (map<uint32, MimeAttribute>::iterator i = fAttributes.begin();
925				i != fAttributes.end(); i++)
926			i->second.Dump();
927
928	if (fDumpIcon || fDumpAll) {
929		if (fSmallIcon != NULL && fSmallIcon->Bits() != NULL) {
930			cout << " \\" << endl << "\t" << kMiniIcon << " ";
931			_DumpIcon((uint8*) fSmallIcon->Bits(), fSmallIcon->BitsLength());
932		}
933
934		if (fBigIcon != NULL && fBigIcon->Bits() != NULL) {
935			cout << " \\" << endl << "\t" << kLargeIcon << " ";
936			_DumpIcon((uint8*) fBigIcon->Bits(), fBigIcon->BitsLength());
937		}
938
939		if (fVectorIcon != NULL && fVectorIconSize != 0) {
940			cout << " \\" << endl << "\t" << kVectorIcon << " ";
941			_DumpIcon((uint8*) fVectorIcon, fVectorIconSize);
942		}
943	}
944
945	cout << endl;
946}
947
948
949void
950MimeType::_DoEdit()
951{
952	if (fDoRemove || fDoForce) {
953		status_t result = Delete();
954		if (result != B_OK)
955			throw Error(strerror(result), result);
956
957		if (fDoRemove)
958			return;
959
960		_PurgeProperties();
961	}
962
963	if (!IsInstalled() && Install() != B_OK)
964		throw Error("could not install mimetype '%s'", Type());
965
966	const char* value = _UserArgValue(kShort);
967	if (value != NULL && (!fDoAdd || fShort.IsEmpty()))
968		if (SetShortDescription(value) != B_OK)
969			throw Error("cannot set %s to %s for '%s'", kShort, value, Type());
970
971	value = _UserArgValue(kLong);
972	if (value != NULL && (!fDoAdd || fLong.IsEmpty()))
973		if (SetLongDescription(value) != B_OK)
974			throw Error("cannot set %s to %s for '%s'", kLong, value, Type());
975
976	value = _UserArgValue(kPreferredApp);
977	if (value != NULL && (!fDoAdd || fPrefApp.IsEmpty())) {
978		entry_ref appHint;
979		if (get_ref_for_path(value, &appHint) != B_OK)
980			throw Error("%s ref_entry for '%s' couldn't be found for '%s'",
981						kPreferredApp, value, Type());
982
983		if (SetAppHint(&appHint) != B_OK)
984			throw Error("cannot set %s to %s for '%s'",
985					kPreferredApp, value, Type());
986	}
987
988	value = _UserArgValue(kPreferredAppSig);
989	if (value != NULL && (!fDoAdd || fPrefAppSig.IsEmpty()))
990		if (SetPreferredApp(value) != B_OK)
991			throw Error("cannot set %s to %s for '%s'",
992					kPreferredAppSig, value, Type());
993
994	value = _UserArgValue(kSniffRule);
995	if (value != NULL && (!fDoAdd || fSniffRule.IsEmpty()))
996		if (SetSnifferRule(value) != B_OK)
997			throw Error("cannot set %s to %s for '%s'",
998					kSniffRule, value, Type());
999
1000	value = _UserArgValue(kMiniIcon);
1001	if (value != NULL && (!fDoAdd || fSmallIcon == NULL)) {
1002		int32 iconSize = strlen(value);
1003		if (iconSize / 2 != B_MINI_ICON * B_MINI_ICON)
1004			throw Error("cannot set %s for '%s'. Hex data size %d is invalid",
1005					kMiniIcon, Type(), iconSize);
1006
1007		_SetIcon(value, B_MINI_ICON);
1008	}
1009
1010	value = _UserArgValue(kLargeIcon);
1011	if (value != NULL && (!fDoAdd || fBigIcon == NULL)) {
1012		int32 iconSize = strlen(value);
1013		if (iconSize / 2 != B_LARGE_ICON * B_LARGE_ICON)
1014			throw Error("cannot set %s for '%s'. Hex data size %d is invalid",
1015					kLargeIcon, Type(), iconSize);
1016
1017		_SetIcon(value, B_LARGE_ICON);
1018	}
1019
1020	value = _UserArgValue(kVectorIcon);
1021	if (value != NULL && (!fDoAdd || fVectorIcon == NULL)) {
1022		int32 iconSize = strlen(value);
1023		if ((iconSize % 2) != 0)
1024			throw Error("cannot set %s for '%s'. Hex data size %d is invalid",
1025					kVectorIcon, Type(), iconSize);
1026
1027		// vector icon size is negative intended
1028		_SetIcon(value, -iconSize);
1029	}
1030
1031	// handle extensions update
1032	pair<TUserArgsI, TUserArgsI> exts
1033							= fUserArguments.equal_range(hash_function(kExtension));
1034	for (TUserArgsI i = exts.first; i != exts.second; i++) {
1035		uint32 key = hash_function(i->second);
1036		if (fExtensions.find(key) == fExtensions.end())
1037			fExtensions.insert(pair<uint32, BString>(key, i->second));
1038	}
1039
1040	if (exts.first != exts.second) {
1041		BMessage msg;
1042		for (map<uint32, BString>::iterator i = fExtensions.begin();
1043				i != fExtensions.end(); i++)
1044			if (msg.AddString("extensions", i->second.String()) != B_OK)
1045				throw Error("extension '%s' couldn't be added",
1046					i->second.String());
1047
1048		if (SetFileExtensions(&msg) != B_OK)
1049			throw Error("set file extensions failed");
1050	}
1051
1052	// take care about attribute trees
1053	for (TUserAttrsI userAttr = fUserAttributes.begin();
1054			userAttr != fUserAttributes.end(); userAttr++ )
1055	{
1056		// search for -attribute "name" in args map
1057		TUserArgsI attrArgs = userAttr->find(hash_function(kAttribute));
1058		if (attrArgs == userAttr->end())
1059			throw Error("internal error: %s arg not found", kAttribute);
1060
1061		// check if we already have this attribute cached
1062		map<uint32, MimeAttribute>::iterator
1063								attr = fAttributes.find(hash_function(attrArgs->second));
1064		if (attr == fAttributes.end()) {
1065			// add new one
1066			MimeAttribute mimeAttr(*userAttr);
1067			fAttributes.insert(
1068				pair<uint32, MimeAttribute>(hash_function(mimeAttr.fName), mimeAttr));
1069		} else if (!fDoAdd)
1070			attr->second.SyncWith(*userAttr);
1071	}
1072
1073	if (fAttributes.size() > 0) {
1074		BMessage msg;
1075		for (map<uint32, MimeAttribute>::iterator i = fAttributes.begin();
1076				i != fAttributes.end(); i++)
1077		{
1078			i->second.StoreInto(&msg);
1079			if (i->second.InitCheck() != B_OK)
1080				throw Error("storing attributes in message failed");
1081		}
1082
1083		if (SetAttrInfo(&msg) != B_OK)
1084			throw Error("set mimetype attributes failed");
1085	}
1086}
1087
1088
1089void
1090MimeType::Process()
1091{
1092	if (fCheckSniffRule) {
1093		TUserArgsI I = fUserArguments.find(fOpMode);
1094		if (I == fUserArguments.end())
1095			throw Error("Sniffer rule is empty");
1096
1097		BString error;
1098		status_t result = BMimeType::CheckSnifferRule(I->second, &error);
1099		if (result == B_OK)
1100			cerr << I->second << endl << "Sniffer rule is correct" << endl;
1101		else
1102			cerr <<  error.String() << endl;
1103
1104		return;
1105	}
1106
1107	if (fDoAdd || fDoSet || fDoForce || fDoRemove) {
1108		_DoEdit();
1109		return;
1110	}
1111
1112	if (fDumpNormal || fDumpRule || fDumpIcon || fDumpAll) {
1113		if (Type() != NULL) {
1114			_Dump(Type());
1115			return;
1116		}
1117
1118		BMessage superTypes;
1119		int32 superCount = 0;
1120		type_code type = B_INT32_TYPE;
1121		if (BMimeType::GetInstalledSupertypes(&superTypes) != B_OK
1122			|| superTypes.GetInfo("super_types", &type, &superCount) != B_OK)
1123			throw Error("super types enumeration failed");
1124
1125		for (int32 si = 0; si < superCount; si++) {
1126			const char* superName = NULL;
1127			if (superTypes.FindString("super_types", si, &superName) != B_OK)
1128				throw Error("name for supertype #%d not found", si);
1129
1130			BMessage types;
1131			if (BMimeType::GetInstalledTypes(superName, &types) != B_OK)
1132				throw Error("mimetypes of supertype '%s' not found", superName);
1133
1134			int32 count = 0;
1135			if (types.GetInfo("types", &type, &count) != B_OK)
1136				continue; // no sub-types?
1137
1138			for (int32 i = 0; i < count; i++) {
1139				const char* name = NULL;
1140				if (types.FindString("types", i, &name) != B_OK)
1141					throw Error("name for type %s/#%d not found", superName, i);
1142
1143				_Dump(name);
1144			}
1145		}
1146	}
1147}
1148
1149
1150int
1151main(int argc, char** argv)
1152{
1153	// AppServer link is required to work with bitmaps
1154	BApplication app("application/x-vnd.haiku.setmime");
1155
1156	try {
1157
1158		if (argc < 2)
1159			throw Error(kNeedArgMessage);
1160
1161		MimeType mimetype(argv);
1162
1163		mimetype.Process();
1164
1165	} catch(exception& exc) {
1166		cerr << argv[0] << " : " << exc.what() << endl;
1167		cerr <<	kUsageMessage;
1168		return B_ERROR;
1169	}
1170
1171	return B_OK;
1172}
1173
1174