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