1//----------------------------------------------------------------------
2//  This software is part of the Haiku distribution and is covered
3//  by the MIT License.
4//---------------------------------------------------------------------
5/*!
6	\file Pattern.cpp
7	MIME sniffer pattern implementation
8*/
9
10#include <sniffer/Err.h>
11#include <sniffer/Pattern.h>
12#include <DataIO.h>
13#include <stdio.h>	// for SEEK_* defines
14#include <new>
15
16#include <AutoDeleter.h>
17
18using namespace BPrivate::Storage::Sniffer;
19
20Pattern::Pattern(const std::string &string, const std::string &mask)
21	: fCStatus(B_NO_INIT)
22	, fErrorMessage(NULL)
23{
24	SetTo(string, mask);
25}
26
27Pattern::Pattern(const std::string &string)
28	: fCStatus(B_NO_INIT)
29	, fErrorMessage(NULL)
30{
31	// Build a mask with all bits turned on of the
32	// appropriate length
33	std::string mask = "";
34	for (uint i = 0; i < string.length(); i++)
35		mask += (char)0xFF;
36	SetTo(string, mask);
37}
38
39Pattern::~Pattern() {
40	delete fErrorMessage;
41}
42
43status_t
44Pattern::InitCheck() const {
45	return fCStatus;
46}
47
48Err*
49Pattern::GetErr() const {
50	if (fCStatus == B_OK)
51		return NULL;
52	else
53		return new(std::nothrow) Err(*fErrorMessage);
54}
55
56void dumpStr(const std::string &string, const char *label = NULL) {
57	if (label)
58		printf("%s: ", label);
59	for (uint i = 0; i < string.length(); i++)
60		printf("%x ", string[i]);
61	printf("\n");
62}
63
64status_t
65Pattern::SetTo(const std::string &string, const std::string &mask) {
66	fString = string;
67	if (fString.length() == 0) {
68		SetStatus(B_BAD_VALUE, "Sniffer pattern error: illegal empty pattern");
69	} else {
70		fMask = mask;
71//		dumpStr(string, "data");
72//		dumpStr(mask, "mask");
73		if (fString.length() != fMask.length()) {
74			SetStatus(B_BAD_VALUE, "Sniffer pattern error: pattern and mask lengths do not match");
75		} else {
76			SetStatus(B_OK);
77		}
78	}
79	return fCStatus;
80}
81
82/*! \brief Looks for a pattern match in the given data stream, starting from
83	each offset withing the given range. Returns true is a match is found,
84	false if not.
85*/
86bool
87Pattern::Sniff(Range range, BPositionIO *data, bool caseInsensitive) const {
88	int32 start = range.Start();
89	int32 end = range.End();
90	off_t size = data->Seek(0, SEEK_END);
91	if (end >= size)
92		end = size-1;	// Don't bother searching beyond the end of the stream
93	for (int i = start; i <= end; i++) {
94		if (Sniff(i, size, data, caseInsensitive))
95			return true;
96	}
97	return false;
98}
99
100// BytesNeeded
101/*! \brief Returns the number of bytes needed to perform a complete sniff, or an error
102	code if something goes wrong.
103*/
104ssize_t
105Pattern::BytesNeeded() const
106{
107	ssize_t result = InitCheck();
108	if (result == B_OK)
109		result = fString.length();
110	return result;
111}
112
113//#define OPTIMIZATION_IS_FOR_CHUMPS
114#if OPTIMIZATION_IS_FOR_CHUMPS
115bool
116Pattern::Sniff(off_t start, off_t size, BPositionIO *data, bool caseInsensitive) const {
117	off_t len = fString.length();
118	char *buffer = new(nothrow) char[len+1];
119	if (buffer) {
120		ArrayDeleter<char> _(buffer);
121		ssize_t bytesRead = data->ReadAt(start, buffer, len);
122		// \todo If there are fewer bytes left in the data stream
123		// from the given position than the length of our data
124		// string, should we just return false (which is what we're
125		// doing now), or should we compare as many bytes as we
126		// can and return true if those match?
127		if (bytesRead < len)
128			return false;
129		else {
130			bool result = true;
131			if (caseInsensitive) {
132				for (int i = 0; i < len; i++) {
133					char secondChar;
134					if ('A' <= fString[i] && fString[i] <= 'Z')
135						secondChar = 'a' + (fString[i] - 'A');	// Also check lowercase
136					else if ('a' <= fString[i] && fString[i] <= 'z')
137						secondChar = 'A' + (fString[i] - 'a');	// Also check uppercase
138					else
139						secondChar = fString[i]; // Check the same char twice as punishment for doing a case insensitive search ;-)
140					if (((fString[i] & fMask[i]) != (buffer[i] & fMask[i]))
141					     && ((secondChar & fMask[i]) != (buffer[i] & fMask[i])))
142					{
143						result = false;
144						break;
145					}
146				}
147			} else {
148				for (int i = 0; i < len; i++) {
149					if ((fString[i] & fMask[i]) != (buffer[i] & fMask[i])) {
150						result = false;
151						break;
152					}
153				}
154			}
155			return result;
156		}
157	} else
158		return false;
159}
160#else
161bool
162Pattern::Sniff(off_t start, off_t size, BPositionIO *data, bool caseInsensitive) const {
163	off_t len = fString.length();
164	char *buffer = new(std::nothrow) char[len+1];
165	if (buffer) {
166		ArrayDeleter<char> _(buffer);
167		ssize_t bytesRead = data->ReadAt(start, buffer, len);
168		// \todo If there are fewer bytes left in the data stream
169		// from the given position than the length of our data
170		// string, should we just return false (which is what we're
171		// doing now), or should we compare as many bytes as we
172		// can and return true if those match?
173		if (bytesRead < len)
174			return false;
175		else {
176			bool result = true;
177			if (caseInsensitive) {
178				for (int i = 0; i < len; i++) {
179					char secondChar;
180					if ('A' <= fString[i] && fString[i] <= 'Z')
181						secondChar = 'a' + (fString[i] - 'A');	// Also check lowercase
182					else if ('a' <= fString[i] && fString[i] <= 'z')
183						secondChar = 'A' + (fString[i] - 'a');	// Also check uppercase
184					else
185						secondChar = fString[i]; // Check the same char twice as punishment for doing a case insensitive search ;-)
186					if (((fString[i] & fMask[i]) != (buffer[i] & fMask[i]))
187					     && ((secondChar & fMask[i]) != (buffer[i] & fMask[i])))
188					{
189						result = false;
190						break;
191					}
192				}
193			} else {
194				for (int i = 0; i < len; i++) {
195					if ((fString[i] & fMask[i]) != (buffer[i] & fMask[i])) {
196						result = false;
197						break;
198					}
199				}
200			}
201			return result;
202		}
203	} else
204		return false;
205}
206#endif
207
208void
209Pattern::SetStatus(status_t status, const char *msg) {
210	fCStatus = status;
211	if (status == B_OK)
212		SetErrorMessage(NULL);
213	else {
214		if (msg)
215			SetErrorMessage(msg);
216		else {
217			SetErrorMessage("Sniffer parser error: Pattern::SetStatus() -- NULL msg with non-B_OK status.\n"
218				"(This is officially the most helpful error message you will ever receive ;-)");
219		}
220	}
221}
222
223void
224Pattern::SetErrorMessage(const char *msg) {
225	delete fErrorMessage;
226	fErrorMessage = (msg) ? (new(std::nothrow) Err(msg, -1)) : (NULL);
227}
228
229
230
231