xpcom/ds/nsPersistentProperties.cpp

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

mercurial