Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "nsArrayEnumerator.h"
8 : #include "nsID.h"
9 : #include "nsCOMArray.h"
10 : #include "nsUnicharInputStream.h"
11 : #include "nsPrintfCString.h"
12 : #include "nsAutoPtr.h"
13 :
14 : #include "nsPersistentProperties.h"
15 : #include "nsIProperties.h"
16 :
17 : #include "mozilla/ArenaAllocatorExtensions.h"
18 :
19 : using mozilla::ArenaStrdup;
20 :
21 : struct PropertyTableEntry : public PLDHashEntryHdr
22 : {
23 : // both of these are arena-allocated
24 : const char* mKey;
25 : const char16_t* mValue;
26 : };
27 :
28 : static const struct PLDHashTableOps property_HashTableOps = {
29 : PLDHashTable::HashStringKey,
30 : PLDHashTable::MatchStringKey,
31 : PLDHashTable::MoveEntryStub,
32 : PLDHashTable::ClearEntryStub,
33 : nullptr,
34 : };
35 :
36 : //
37 : // parser stuff
38 : //
39 : enum EParserState
40 : {
41 : eParserState_AwaitingKey,
42 : eParserState_Key,
43 : eParserState_AwaitingValue,
44 : eParserState_Value,
45 : eParserState_Comment
46 : };
47 :
48 : enum EParserSpecial
49 : {
50 : eParserSpecial_None, // not parsing a special character
51 : eParserSpecial_Escaped, // awaiting a special character
52 : eParserSpecial_Unicode // parsing a \Uxxx value
53 : };
54 :
55 21 : class MOZ_STACK_CLASS nsPropertiesParser
56 : {
57 : public:
58 21 : explicit nsPropertiesParser(nsIPersistentProperties* aProps)
59 21 : : mHaveMultiLine(false)
60 : , mState(eParserState_AwaitingKey)
61 21 : , mProps(aProps)
62 : {
63 21 : }
64 :
65 1300 : void FinishValueState(nsAString& aOldValue)
66 : {
67 : static const char trimThese[] = " \t";
68 1300 : mKey.Trim(trimThese, false, true);
69 :
70 : // This is really ugly hack but it should be fast
71 : char16_t backup_char;
72 1300 : uint32_t minLength = mMinLength;
73 1300 : if (minLength) {
74 6 : backup_char = mValue[minLength - 1];
75 6 : mValue.SetCharAt('x', minLength - 1);
76 : }
77 1300 : mValue.Trim(trimThese, false, true);
78 1300 : if (minLength) {
79 6 : mValue.SetCharAt(backup_char, minLength - 1);
80 : }
81 :
82 1300 : mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue);
83 1300 : mSpecialState = eParserSpecial_None;
84 1300 : WaitForKey();
85 1300 : }
86 :
87 21 : EParserState GetState() { return mState; }
88 :
89 : static nsresult SegmentWriter(nsIUnicharInputStream* aStream,
90 : void* aClosure,
91 : const char16_t* aFromSegment,
92 : uint32_t aToOffset,
93 : uint32_t aCount,
94 : uint32_t* aWriteCount);
95 :
96 : nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength);
97 :
98 : private:
99 : bool ParseValueCharacter(
100 : char16_t aChar, // character that is just being parsed
101 : const char16_t* aCur, // pointer to character aChar in the buffer
102 : const char16_t*& aTokenStart, // string copying is done in blocks as big as
103 : // possible, aTokenStart points to the beginning
104 : // of this block
105 : nsAString& aOldValue); // when duplicate property is found, new value
106 : // is stored into hashtable and the old one is
107 : // placed in this variable
108 :
109 1952 : void WaitForKey()
110 : {
111 1952 : mState = eParserState_AwaitingKey;
112 1952 : }
113 :
114 1300 : void EnterKeyState()
115 : {
116 1300 : mKey.Truncate();
117 1300 : mState = eParserState_Key;
118 1300 : }
119 :
120 1300 : void WaitForValue()
121 : {
122 1300 : mState = eParserState_AwaitingValue;
123 1300 : }
124 :
125 1300 : void EnterValueState()
126 : {
127 1300 : mValue.Truncate();
128 1300 : mMinLength = 0;
129 1300 : mState = eParserState_Value;
130 1300 : mSpecialState = eParserSpecial_None;
131 1300 : }
132 :
133 652 : void EnterCommentState()
134 : {
135 652 : mState = eParserState_Comment;
136 652 : }
137 :
138 : nsAutoString mKey;
139 : nsAutoString mValue;
140 :
141 : uint32_t mUnicodeValuesRead; // should be 4!
142 : char16_t mUnicodeValue; // currently parsed unicode value
143 : bool mHaveMultiLine; // is TRUE when last processed characters form
144 : // any of following sequences:
145 : // - "\\\r"
146 : // - "\\\n"
147 : // - "\\\r\n"
148 : // - any sequence above followed by any
149 : // combination of ' ' and '\t'
150 : bool mMultiLineCanSkipN; // TRUE if "\\\r" was detected
151 : uint32_t mMinLength; // limit right trimming at the end to not trim
152 : // escaped whitespaces
153 : EParserState mState;
154 : // if we see a '\' then we enter this special state
155 : EParserSpecial mSpecialState;
156 : nsCOMPtr<nsIPersistentProperties> mProps;
157 : };
158 :
159 : inline bool
160 3075 : IsWhiteSpace(char16_t aChar)
161 : {
162 2835 : return (aChar == ' ') || (aChar == '\t') ||
163 5910 : (aChar == '\r') || (aChar == '\n');
164 : }
165 :
166 : inline bool
167 1540 : IsEOL(char16_t aChar)
168 : {
169 1540 : return (aChar == '\r') || (aChar == '\n');
170 : }
171 :
172 :
173 : bool
174 42640 : nsPropertiesParser::ParseValueCharacter(char16_t aChar, const char16_t* aCur,
175 : const char16_t*& aTokenStart,
176 : nsAString& aOldValue)
177 : {
178 42640 : switch (mSpecialState) {
179 : // the normal state - look for special characters
180 : case eParserSpecial_None:
181 42623 : switch (aChar) {
182 : case '\\':
183 13 : if (mHaveMultiLine) {
184 : // there is nothing to append to mValue yet
185 0 : mHaveMultiLine = false;
186 : } else {
187 13 : mValue += Substring(aTokenStart, aCur);
188 : }
189 :
190 13 : mSpecialState = eParserSpecial_Escaped;
191 13 : break;
192 :
193 : case '\n':
194 : // if we detected multiline and got only "\\\r" ignore next "\n" if any
195 1296 : if (mHaveMultiLine && mMultiLineCanSkipN) {
196 : // but don't allow another '\n' to be skipped
197 0 : mMultiLineCanSkipN = false;
198 : // Now there is nothing to append to the mValue since we are skipping
199 : // whitespaces at the beginning of the new line of the multiline
200 : // property. Set aTokenStart properly to ensure that nothing is appended
201 : // if we find regular line-end or the end of the buffer.
202 0 : aTokenStart = aCur + 1;
203 0 : break;
204 : }
205 : MOZ_FALLTHROUGH;
206 :
207 : case '\r':
208 : // we're done! We have a key and value
209 1296 : mValue += Substring(aTokenStart, aCur);
210 1296 : FinishValueState(aOldValue);
211 1296 : mHaveMultiLine = false;
212 1296 : break;
213 :
214 : default:
215 : // there is nothing to do with normal characters,
216 : // but handle multilines correctly
217 41314 : if (mHaveMultiLine) {
218 0 : if (aChar == ' ' || aChar == '\t') {
219 : // don't allow another '\n' to be skipped
220 0 : mMultiLineCanSkipN = false;
221 : // Now there is nothing to append to the mValue since we are skipping
222 : // whitespaces at the beginning of the new line of the multiline
223 : // property. Set aTokenStart properly to ensure that nothing is appended
224 : // if we find regular line-end or the end of the buffer.
225 0 : aTokenStart = aCur + 1;
226 0 : break;
227 : }
228 0 : mHaveMultiLine = false;
229 0 : aTokenStart = aCur;
230 : }
231 41314 : break; // from switch on (aChar)
232 : }
233 42623 : break; // from switch on (mSpecialState)
234 :
235 : // saw a \ character, so parse the character after that
236 : case eParserSpecial_Escaped:
237 : // probably want to start parsing at the next token
238 : // other characters, like 'u' might override this
239 13 : aTokenStart = aCur + 1;
240 13 : mSpecialState = eParserSpecial_None;
241 :
242 13 : switch (aChar) {
243 : // the easy characters - \t, \n, and so forth
244 : case 't':
245 0 : mValue += char16_t('\t');
246 0 : mMinLength = mValue.Length();
247 0 : break;
248 : case 'n':
249 12 : mValue += char16_t('\n');
250 12 : mMinLength = mValue.Length();
251 12 : break;
252 : case 'r':
253 0 : mValue += char16_t('\r');
254 0 : mMinLength = mValue.Length();
255 0 : break;
256 : case '\\':
257 0 : mValue += char16_t('\\');
258 0 : break;
259 :
260 : // switch to unicode mode!
261 : case 'u':
262 : case 'U':
263 1 : mSpecialState = eParserSpecial_Unicode;
264 1 : mUnicodeValuesRead = 0;
265 1 : mUnicodeValue = 0;
266 1 : break;
267 :
268 : // a \ immediately followed by a newline means we're going multiline
269 : case '\r':
270 : case '\n':
271 0 : mHaveMultiLine = true;
272 0 : mMultiLineCanSkipN = (aChar == '\r');
273 0 : mSpecialState = eParserSpecial_None;
274 0 : break;
275 :
276 : default:
277 : // don't recognize the character, so just append it
278 0 : mValue += aChar;
279 0 : break;
280 : }
281 13 : break;
282 :
283 : // we're in the middle of parsing a 4-character unicode value
284 : // like \u5f39
285 : case eParserSpecial_Unicode:
286 4 : if ('0' <= aChar && aChar <= '9') {
287 2 : mUnicodeValue =
288 4 : (mUnicodeValue << 4) | (aChar - '0');
289 2 : } else if ('a' <= aChar && aChar <= 'f') {
290 2 : mUnicodeValue =
291 4 : (mUnicodeValue << 4) | (aChar - 'a' + 0x0a);
292 0 : } else if ('A' <= aChar && aChar <= 'F') {
293 0 : mUnicodeValue =
294 0 : (mUnicodeValue << 4) | (aChar - 'A' + 0x0a);
295 : } else {
296 : // non-hex character. Append what we have, and move on.
297 0 : mValue += mUnicodeValue;
298 0 : mMinLength = mValue.Length();
299 0 : mSpecialState = eParserSpecial_None;
300 :
301 : // leave aTokenStart at this unknown character, so it gets appended
302 0 : aTokenStart = aCur;
303 :
304 : // ensure parsing this non-hex character again
305 0 : return false;
306 : }
307 :
308 4 : if (++mUnicodeValuesRead >= 4) {
309 1 : aTokenStart = aCur + 1;
310 1 : mSpecialState = eParserSpecial_None;
311 1 : mValue += mUnicodeValue;
312 1 : mMinLength = mValue.Length();
313 : }
314 :
315 4 : break;
316 : }
317 :
318 42640 : return true;
319 : }
320 :
321 : nsresult
322 42 : nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream,
323 : void* aClosure,
324 : const char16_t* aFromSegment,
325 : uint32_t aToOffset,
326 : uint32_t aCount,
327 : uint32_t* aWriteCount)
328 : {
329 42 : nsPropertiesParser* parser = static_cast<nsPropertiesParser*>(aClosure);
330 42 : parser->ParseBuffer(aFromSegment, aCount);
331 :
332 42 : *aWriteCount = aCount;
333 42 : return NS_OK;
334 : }
335 :
336 : nsresult
337 42 : nsPropertiesParser::ParseBuffer(const char16_t* aBuffer,
338 : uint32_t aBufferLength)
339 : {
340 42 : const char16_t* cur = aBuffer;
341 42 : const char16_t* end = aBuffer + aBufferLength;
342 :
343 : // points to the start/end of the current key or value
344 42 : const char16_t* tokenStart = nullptr;
345 :
346 : // if we're in the middle of parsing a key or value, make sure
347 : // the current token points to the beginning of the current buffer
348 75 : if (mState == eParserState_Key ||
349 33 : mState == eParserState_Value) {
350 18 : tokenStart = aBuffer;
351 : }
352 :
353 84 : nsAutoString oldValue;
354 :
355 230026 : while (cur != end) {
356 :
357 114992 : char16_t c = *cur;
358 :
359 114992 : switch (mState) {
360 : case eParserState_AwaitingKey:
361 2191 : if (c == '#' || c == '!') {
362 652 : EnterCommentState();
363 : }
364 :
365 1539 : else if (!IsWhiteSpace(c)) {
366 : // not a comment, not whitespace, we must have found a key!
367 1300 : EnterKeyState();
368 1300 : tokenStart = cur;
369 : }
370 2191 : break;
371 :
372 : case eParserState_Key:
373 30684 : if (c == '=' || c == ':') {
374 1300 : mKey += Substring(tokenStart, cur);
375 1300 : WaitForValue();
376 : }
377 30684 : break;
378 :
379 : case eParserState_AwaitingValue:
380 1540 : if (IsEOL(c)) {
381 : // no value at all! mimic the normal value-ending
382 4 : EnterValueState();
383 4 : FinishValueState(oldValue);
384 : }
385 :
386 : // ignore white space leading up to the value
387 1536 : else if (!IsWhiteSpace(c)) {
388 1296 : tokenStart = cur;
389 1296 : EnterValueState();
390 :
391 : // make sure to handle this first character
392 1296 : if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
393 1296 : cur++;
394 : }
395 : // If the character isn't consumed, don't do cur++ and parse
396 : // the character again. This can happen f.e. for char 'X' in sequence
397 : // "\u00X". This character can be control character and must be
398 : // processed again.
399 1296 : continue;
400 : }
401 244 : break;
402 :
403 : case eParserState_Value:
404 41344 : if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
405 41344 : cur++;
406 : }
407 : // See few lines above for reason of doing this
408 41344 : continue;
409 :
410 : case eParserState_Comment:
411 : // stay in this state till we hit EOL
412 39233 : if (c == '\r' || c == '\n') {
413 652 : WaitForKey();
414 : }
415 39233 : break;
416 : }
417 :
418 : // finally, advance to the next character
419 72352 : cur++;
420 : }
421 :
422 : // if we're still parsing the value and are in eParserSpecial_None, then
423 : // append whatever we have..
424 51 : if (mState == eParserState_Value && tokenStart &&
425 9 : mSpecialState == eParserSpecial_None) {
426 9 : mValue += Substring(tokenStart, cur);
427 : }
428 : // if we're still parsing the key, then append whatever we have..
429 33 : else if (mState == eParserState_Key && tokenStart) {
430 9 : mKey += Substring(tokenStart, cur);
431 : }
432 :
433 84 : return NS_OK;
434 : }
435 :
436 21 : nsPersistentProperties::nsPersistentProperties()
437 : : mIn(nullptr)
438 : , mTable(&property_HashTableOps, sizeof(PropertyTableEntry), 16)
439 21 : , mArena()
440 : {
441 21 : }
442 :
443 0 : nsPersistentProperties::~nsPersistentProperties()
444 : {
445 0 : }
446 :
447 : nsresult
448 21 : nsPersistentProperties::Create(nsISupports* aOuter, REFNSIID aIID,
449 : void** aResult)
450 : {
451 21 : if (aOuter) {
452 0 : return NS_ERROR_NO_AGGREGATION;
453 : }
454 42 : RefPtr<nsPersistentProperties> props = new nsPersistentProperties();
455 21 : return props->QueryInterface(aIID, aResult);
456 : }
457 :
458 252 : NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties, nsIProperties)
459 :
460 : NS_IMETHODIMP
461 21 : nsPersistentProperties::Load(nsIInputStream* aIn)
462 : {
463 21 : nsresult rv = NS_NewUnicharInputStream(aIn, getter_AddRefs(mIn));
464 :
465 21 : if (rv != NS_OK) {
466 0 : NS_WARNING("Error creating UnicharInputStream");
467 0 : return NS_ERROR_FAILURE;
468 : }
469 :
470 42 : nsPropertiesParser parser(this);
471 :
472 : uint32_t nProcessed;
473 : // If this 4096 is changed to some other value, make sure to adjust
474 : // the bug121341.properties test file accordingly.
475 189 : while (NS_SUCCEEDED(rv = mIn->ReadSegments(nsPropertiesParser::SegmentWriter,
476 189 : &parser, 4096, &nProcessed)) &&
477 63 : nProcessed != 0);
478 21 : mIn = nullptr;
479 21 : if (NS_FAILED(rv)) {
480 0 : return rv;
481 : }
482 :
483 : // We may have an unprocessed value at this point
484 : // if the last line did not have a proper line ending.
485 21 : if (parser.GetState() == eParserState_Value) {
486 0 : nsAutoString oldValue;
487 0 : parser.FinishValueState(oldValue);
488 : }
489 :
490 21 : return NS_OK;
491 : }
492 :
493 : NS_IMETHODIMP
494 1300 : nsPersistentProperties::SetStringProperty(const nsACString& aKey,
495 : const nsAString& aNewValue,
496 : nsAString& aOldValue)
497 : {
498 2600 : const nsCString& flatKey = PromiseFlatCString(aKey);
499 : auto entry = static_cast<PropertyTableEntry*>
500 1300 : (mTable.Add(flatKey.get()));
501 :
502 1300 : if (entry->mKey) {
503 0 : aOldValue = entry->mValue;
504 0 : NS_WARNING(nsPrintfCString("the property %s already exists",
505 0 : flatKey.get()).get());
506 : } else {
507 1300 : aOldValue.Truncate();
508 : }
509 :
510 1300 : entry->mKey = ArenaStrdup(flatKey, mArena);
511 1300 : entry->mValue = ArenaStrdup(aNewValue, mArena);
512 :
513 2600 : return NS_OK;
514 : }
515 :
516 : NS_IMETHODIMP
517 0 : nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader)
518 : {
519 0 : return NS_ERROR_NOT_IMPLEMENTED;
520 : }
521 :
522 : NS_IMETHODIMP
523 155 : nsPersistentProperties::GetStringProperty(const nsACString& aKey,
524 : nsAString& aValue)
525 : {
526 310 : const nsCString& flatKey = PromiseFlatCString(aKey);
527 :
528 155 : auto entry = static_cast<PropertyTableEntry*>(mTable.Search(flatKey.get()));
529 155 : if (!entry) {
530 7 : return NS_ERROR_FAILURE;
531 : }
532 :
533 148 : aValue = entry->mValue;
534 148 : return NS_OK;
535 : }
536 :
537 : NS_IMETHODIMP
538 0 : nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult)
539 : {
540 0 : nsCOMArray<nsIPropertyElement> props;
541 :
542 : // We know the necessary size; we can avoid growing it while adding elements
543 0 : props.SetCapacity(mTable.EntryCount());
544 :
545 : // Step through hash entries populating a transient array
546 0 : for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
547 0 : auto entry = static_cast<PropertyTableEntry*>(iter.Get());
548 :
549 : RefPtr<nsPropertyElement> element =
550 0 : new nsPropertyElement(nsDependentCString(entry->mKey),
551 0 : nsDependentString(entry->mValue));
552 :
553 0 : if (!props.AppendObject(element)) {
554 0 : return NS_ERROR_OUT_OF_MEMORY;
555 : }
556 : }
557 :
558 0 : return NS_NewArrayEnumerator(aResult, props);
559 : }
560 :
561 : ////////////////////////////////////////////////////////////////////////////////
562 : // XXX Some day we'll unify the nsIPersistentProperties interface with
563 : // nsIProperties, but until now...
564 :
565 : NS_IMETHODIMP
566 0 : nsPersistentProperties::Get(const char* aProp, const nsIID& aUUID,
567 : void** aResult)
568 : {
569 0 : return NS_ERROR_NOT_IMPLEMENTED;
570 : }
571 :
572 : NS_IMETHODIMP
573 0 : nsPersistentProperties::Set(const char* aProp, nsISupports* value)
574 : {
575 0 : return NS_ERROR_NOT_IMPLEMENTED;
576 : }
577 : NS_IMETHODIMP
578 0 : nsPersistentProperties::Undefine(const char* aProp)
579 : {
580 0 : return NS_ERROR_NOT_IMPLEMENTED;
581 : }
582 :
583 : NS_IMETHODIMP
584 0 : nsPersistentProperties::Has(const char* aProp, bool* aResult)
585 : {
586 0 : *aResult = !!mTable.Search(aProp);
587 0 : return NS_OK;
588 : }
589 :
590 : NS_IMETHODIMP
591 0 : nsPersistentProperties::GetKeys(uint32_t* aCount, char*** aKeys)
592 : {
593 0 : return NS_ERROR_NOT_IMPLEMENTED;
594 : }
595 :
596 : ////////////////////////////////////////////////////////////////////////////////
597 : // PropertyElement
598 : ////////////////////////////////////////////////////////////////////////////////
599 :
600 : nsresult
601 0 : nsPropertyElement::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult)
602 : {
603 0 : if (aOuter) {
604 0 : return NS_ERROR_NO_AGGREGATION;
605 : }
606 0 : RefPtr<nsPropertyElement> propElem = new nsPropertyElement();
607 0 : return propElem->QueryInterface(aIID, aResult);
608 : }
609 :
610 0 : NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement)
611 :
612 : NS_IMETHODIMP
613 0 : nsPropertyElement::GetKey(nsACString& aReturnKey)
614 : {
615 0 : aReturnKey = mKey;
616 0 : return NS_OK;
617 : }
618 :
619 : NS_IMETHODIMP
620 0 : nsPropertyElement::GetValue(nsAString& aReturnValue)
621 : {
622 0 : aReturnValue = mValue;
623 0 : return NS_OK;
624 : }
625 :
626 : NS_IMETHODIMP
627 0 : nsPropertyElement::SetKey(const nsACString& aKey)
628 : {
629 0 : mKey = aKey;
630 0 : return NS_OK;
631 : }
632 :
633 : NS_IMETHODIMP
634 0 : nsPropertyElement::SetValue(const nsAString& aValue)
635 : {
636 0 : mValue = aValue;
637 0 : return NS_OK;
638 : }
639 :
640 : ////////////////////////////////////////////////////////////////////////////////
|