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/. */
7 #include "nsArrayEnumerator.h"
9 #include "nsCOMArray.h"
10 #include "nsUnicharInputStream.h"
11 #include "nsPrintfCString.h"
13 #include "nsPersistentProperties.h"
14 #include "nsIProperties.h"
16 #include "mozilla/ArenaAllocatorExtensions.h"
18 using mozilla::ArenaStrdup
;
20 struct PropertyTableEntry
: public PLDHashEntryHdr
{
21 // both of these are arena-allocated
23 const char16_t
* mValue
;
26 static const struct PLDHashTableOps property_HashTableOps
= {
27 PLDHashTable::HashStringKey
,
28 PLDHashTable::MatchStringKey
,
29 PLDHashTable::MoveEntryStub
,
30 PLDHashTable::ClearEntryStub
,
38 eParserState_AwaitingKey
,
40 eParserState_AwaitingValue
,
46 eParserSpecial_None
, // not parsing a special character
47 eParserSpecial_Escaped
, // awaiting a special character
48 eParserSpecial_Unicode
// parsing a \Uxxx value
51 class MOZ_STACK_CLASS nsPropertiesParser
{
53 explicit nsPropertiesParser(nsIPersistentProperties
* aProps
)
54 : mUnicodeValuesRead(0),
56 mHaveMultiLine(false),
57 mMultiLineCanSkipN(false),
59 mState(eParserState_AwaitingKey
),
60 mSpecialState(eParserSpecial_None
),
63 void FinishValueState(nsAString
& aOldValue
) {
64 static const char trimThese
[] = " \t";
65 mKey
.Trim(trimThese
, false, true);
67 // This is really ugly hack but it should be fast
69 uint32_t minLength
= mMinLength
;
71 backup_char
= mValue
[minLength
- 1];
72 mValue
.SetCharAt('x', minLength
- 1);
74 mValue
.Trim(trimThese
, false, true);
76 mValue
.SetCharAt(backup_char
, minLength
- 1);
79 mProps
->SetStringProperty(NS_ConvertUTF16toUTF8(mKey
), mValue
, aOldValue
);
80 mSpecialState
= eParserSpecial_None
;
84 EParserState
GetState() { return mState
; }
86 static nsresult
SegmentWriter(nsIUnicharInputStream
* aStream
, void* aClosure
,
87 const char16_t
* aFromSegment
,
88 uint32_t aToOffset
, uint32_t aCount
,
89 uint32_t* aWriteCount
);
91 nsresult
ParseBuffer(const char16_t
* aBuffer
, uint32_t aBufferLength
);
94 bool ParseValueCharacter(
95 char16_t aChar
, // character that is just being parsed
96 const char16_t
* aCur
, // pointer to character aChar in the buffer
97 const char16_t
*& aTokenStart
, // string copying is done in blocks as big
98 // as possible, aTokenStart points to the
99 // beginning of this block
100 nsAString
& aOldValue
); // when duplicate property is found, new value
101 // is stored into hashtable and the old one is
102 // placed in this variable
104 void WaitForKey() { mState
= eParserState_AwaitingKey
; }
106 void EnterKeyState() {
108 mState
= eParserState_Key
;
111 void WaitForValue() { mState
= eParserState_AwaitingValue
; }
113 void EnterValueState() {
116 mState
= eParserState_Value
;
117 mSpecialState
= eParserSpecial_None
;
120 void EnterCommentState() { mState
= eParserState_Comment
; }
125 uint32_t mUnicodeValuesRead
; // should be 4!
126 char16_t mUnicodeValue
; // currently parsed unicode value
127 bool mHaveMultiLine
; // is TRUE when last processed characters form
128 // any of following sequences:
132 // - any sequence above followed by any
133 // combination of ' ' and '\t'
134 bool mMultiLineCanSkipN
; // TRUE if "\\\r" was detected
135 uint32_t mMinLength
; // limit right trimming at the end to not trim
136 // escaped whitespaces
138 // if we see a '\' then we enter this special state
139 EParserSpecial mSpecialState
;
140 nsCOMPtr
<nsIPersistentProperties
> mProps
;
143 inline bool IsWhiteSpace(char16_t aChar
) {
144 return (aChar
== ' ') || (aChar
== '\t') || (aChar
== '\r') ||
148 inline bool IsEOL(char16_t aChar
) { return (aChar
== '\r') || (aChar
== '\n'); }
150 bool nsPropertiesParser::ParseValueCharacter(char16_t aChar
,
151 const char16_t
* aCur
,
152 const char16_t
*& aTokenStart
,
153 nsAString
& aOldValue
) {
154 switch (mSpecialState
) {
155 // the normal state - look for special characters
156 case eParserSpecial_None
:
159 if (mHaveMultiLine
) {
160 // there is nothing to append to mValue yet
161 mHaveMultiLine
= false;
163 mValue
+= Substring(aTokenStart
, aCur
);
166 mSpecialState
= eParserSpecial_Escaped
;
170 // if we detected multiline and got only "\\\r" ignore next "\n" if
172 if (mHaveMultiLine
&& mMultiLineCanSkipN
) {
173 // but don't allow another '\n' to be skipped
174 mMultiLineCanSkipN
= false;
175 // Now there is nothing to append to the mValue since we are
176 // skipping whitespaces at the beginning of the new line of the
177 // multiline property. Set aTokenStart properly to ensure that
178 // nothing is appended if we find regular line-end or the end of the
180 aTokenStart
= aCur
+ 1;
186 // we're done! We have a key and value
187 mValue
+= Substring(aTokenStart
, aCur
);
188 FinishValueState(aOldValue
);
189 mHaveMultiLine
= false;
193 // there is nothing to do with normal characters,
194 // but handle multilines correctly
195 if (mHaveMultiLine
) {
196 if (aChar
== ' ' || aChar
== '\t') {
197 // don't allow another '\n' to be skipped
198 mMultiLineCanSkipN
= false;
199 // Now there is nothing to append to the mValue since we are
200 // skipping whitespaces at the beginning of the new line of the
201 // multiline property. Set aTokenStart properly to ensure that
202 // nothing is appended if we find regular line-end or the end of
204 aTokenStart
= aCur
+ 1;
207 mHaveMultiLine
= false;
210 break; // from switch on (aChar)
212 break; // from switch on (mSpecialState)
214 // saw a \ character, so parse the character after that
215 case eParserSpecial_Escaped
:
216 // probably want to start parsing at the next token
217 // other characters, like 'u' might override this
218 aTokenStart
= aCur
+ 1;
219 mSpecialState
= eParserSpecial_None
;
222 // the easy characters - \t, \n, and so forth
224 mValue
+= char16_t('\t');
225 mMinLength
= mValue
.Length();
228 mValue
+= char16_t('\n');
229 mMinLength
= mValue
.Length();
232 mValue
+= char16_t('\r');
233 mMinLength
= mValue
.Length();
236 mValue
+= char16_t('\\');
239 // switch to unicode mode!
242 mSpecialState
= eParserSpecial_Unicode
;
243 mUnicodeValuesRead
= 0;
247 // a \ immediately followed by a newline means we're going multiline
250 mHaveMultiLine
= true;
251 mMultiLineCanSkipN
= (aChar
== '\r');
252 mSpecialState
= eParserSpecial_None
;
256 // don't recognize the character, so just append it
262 // we're in the middle of parsing a 4-character unicode value
264 case eParserSpecial_Unicode
:
265 if ('0' <= aChar
&& aChar
<= '9') {
266 mUnicodeValue
= (mUnicodeValue
<< 4) | (aChar
- '0');
267 } else if ('a' <= aChar
&& aChar
<= 'f') {
268 mUnicodeValue
= (mUnicodeValue
<< 4) | (aChar
- 'a' + 0x0a);
269 } else if ('A' <= aChar
&& aChar
<= 'F') {
270 mUnicodeValue
= (mUnicodeValue
<< 4) | (aChar
- 'A' + 0x0a);
272 // non-hex character. Append what we have, and move on.
273 mValue
+= mUnicodeValue
;
274 mMinLength
= mValue
.Length();
275 mSpecialState
= eParserSpecial_None
;
277 // leave aTokenStart at this unknown character, so it gets appended
280 // ensure parsing this non-hex character again
284 if (++mUnicodeValuesRead
>= 4) {
285 aTokenStart
= aCur
+ 1;
286 mSpecialState
= eParserSpecial_None
;
287 mValue
+= mUnicodeValue
;
288 mMinLength
= mValue
.Length();
297 nsresult
nsPropertiesParser::SegmentWriter(nsIUnicharInputStream
* aStream
,
299 const char16_t
* aFromSegment
,
300 uint32_t aToOffset
, uint32_t aCount
,
301 uint32_t* aWriteCount
) {
302 nsPropertiesParser
* parser
= static_cast<nsPropertiesParser
*>(aClosure
);
303 parser
->ParseBuffer(aFromSegment
, aCount
);
305 *aWriteCount
= aCount
;
309 nsresult
nsPropertiesParser::ParseBuffer(const char16_t
* aBuffer
,
310 uint32_t aBufferLength
) {
311 const char16_t
* cur
= aBuffer
;
312 const char16_t
* end
= aBuffer
+ aBufferLength
;
314 // points to the start/end of the current key or value
315 const char16_t
* tokenStart
= nullptr;
317 // if we're in the middle of parsing a key or value, make sure
318 // the current token points to the beginning of the current buffer
319 if (mState
== eParserState_Key
|| mState
== eParserState_Value
) {
320 tokenStart
= aBuffer
;
323 nsAutoString oldValue
;
329 case eParserState_AwaitingKey
:
330 if (c
== '#' || c
== '!') {
334 else if (!IsWhiteSpace(c
)) {
335 // not a comment, not whitespace, we must have found a key!
341 case eParserState_Key
:
342 if (c
== '=' || c
== ':') {
343 mKey
+= Substring(tokenStart
, cur
);
348 case eParserState_AwaitingValue
:
350 // no value at all! mimic the normal value-ending
352 FinishValueState(oldValue
);
355 // ignore white space leading up to the value
356 else if (!IsWhiteSpace(c
)) {
360 // make sure to handle this first character
361 if (ParseValueCharacter(c
, cur
, tokenStart
, oldValue
)) {
364 // If the character isn't consumed, don't do cur++ and parse
365 // the character again. This can happen f.e. for char 'X' in sequence
366 // "\u00X". This character can be control character and must be
372 case eParserState_Value
:
373 if (ParseValueCharacter(c
, cur
, tokenStart
, oldValue
)) {
376 // See few lines above for reason of doing this
379 case eParserState_Comment
:
380 // stay in this state till we hit EOL
381 if (c
== '\r' || c
== '\n') {
387 // finally, advance to the next character
391 // if we're still parsing the value and are in eParserSpecial_None, then
392 // append whatever we have..
393 if (mState
== eParserState_Value
&& tokenStart
&&
394 mSpecialState
== eParserSpecial_None
) {
395 mValue
+= Substring(tokenStart
, cur
);
397 // if we're still parsing the key, then append whatever we have..
398 else if (mState
== eParserState_Key
&& tokenStart
) {
399 mKey
+= Substring(tokenStart
, cur
);
405 nsPersistentProperties::nsPersistentProperties()
407 mTable(&property_HashTableOps
, sizeof(PropertyTableEntry
), 16),
410 nsPersistentProperties::~nsPersistentProperties() = default;
412 size_t nsPersistentProperties::SizeOfIncludingThis(
413 mozilla::MallocSizeOf aMallocSizeOf
) const {
414 // The memory used by mTable is accounted for in mArena.
416 n
+= mArena
.SizeOfExcludingThis(aMallocSizeOf
);
417 n
+= mTable
.ShallowSizeOfExcludingThis(aMallocSizeOf
);
418 return aMallocSizeOf(this) + n
;
421 NS_IMPL_ISUPPORTS(nsPersistentProperties
, nsIPersistentProperties
,
425 nsPersistentProperties::Load(nsIInputStream
* aIn
) {
426 nsresult rv
= NS_NewUnicharInputStream(aIn
, getter_AddRefs(mIn
));
429 NS_WARNING("Error creating UnicharInputStream");
430 return NS_ERROR_FAILURE
;
433 nsPropertiesParser
parser(this);
436 // If this 4096 is changed to some other value, make sure to adjust
437 // the bug121341.properties test file accordingly.
438 while (NS_SUCCEEDED(rv
= mIn
->ReadSegments(nsPropertiesParser::SegmentWriter
,
439 &parser
, 4096, &nProcessed
)) &&
447 // We may have an unprocessed value at this point
448 // if the last line did not have a proper line ending.
449 if (parser
.GetState() == eParserState_Value
) {
450 nsAutoString oldValue
;
451 parser
.FinishValueState(oldValue
);
458 nsPersistentProperties::SetStringProperty(const nsACString
& aKey
,
459 const nsAString
& aNewValue
,
460 nsAString
& aOldValue
) {
461 const nsCString
& flatKey
= PromiseFlatCString(aKey
);
462 auto entry
= static_cast<PropertyTableEntry
*>(mTable
.Add(flatKey
.get()));
465 aOldValue
= entry
->mValue
;
467 nsPrintfCString("the property %s already exists", flatKey
.get()).get());
469 aOldValue
.Truncate();
472 entry
->mKey
= ArenaStrdup(flatKey
, mArena
);
473 entry
->mValue
= ArenaStrdup(aNewValue
, mArena
);
479 nsPersistentProperties::Save(nsIOutputStream
* aOut
, const nsACString
& aHeader
) {
480 return NS_ERROR_NOT_IMPLEMENTED
;
484 nsPersistentProperties::GetStringProperty(const nsACString
& aKey
,
486 const nsCString
& flatKey
= PromiseFlatCString(aKey
);
488 auto entry
= static_cast<PropertyTableEntry
*>(mTable
.Search(flatKey
.get()));
490 return NS_ERROR_FAILURE
;
493 aValue
= entry
->mValue
;
498 nsPersistentProperties::Enumerate(nsISimpleEnumerator
** aResult
) {
499 nsCOMArray
<nsIPropertyElement
> props
;
501 // We know the necessary size; we can avoid growing it while adding elements
502 props
.SetCapacity(mTable
.EntryCount());
504 // Step through hash entries populating a transient array
505 for (auto iter
= mTable
.Iter(); !iter
.Done(); iter
.Next()) {
506 auto entry
= static_cast<PropertyTableEntry
*>(iter
.Get());
508 RefPtr
<nsPropertyElement
> element
= new nsPropertyElement(
509 nsDependentCString(entry
->mKey
), nsDependentString(entry
->mValue
));
511 if (!props
.AppendObject(element
)) {
512 return NS_ERROR_OUT_OF_MEMORY
;
516 return NS_NewArrayEnumerator(aResult
, props
, NS_GET_IID(nsIPropertyElement
));
519 ////////////////////////////////////////////////////////////////////////////////
520 // XXX Some day we'll unify the nsIPersistentProperties interface with
521 // nsIProperties, but until now...
524 nsPersistentProperties::Get(const char* aProp
, const nsIID
& aUUID
,
526 return NS_ERROR_NOT_IMPLEMENTED
;
530 nsPersistentProperties::Set(const char* aProp
, nsISupports
* value
) {
531 return NS_ERROR_NOT_IMPLEMENTED
;
534 nsPersistentProperties::Undefine(const char* aProp
) {
535 return NS_ERROR_NOT_IMPLEMENTED
;
539 nsPersistentProperties::Has(const char* aProp
, bool* aResult
) {
540 *aResult
= !!mTable
.Search(aProp
);
545 nsPersistentProperties::GetKeys(nsTArray
<nsCString
>& aKeys
) {
546 return NS_ERROR_NOT_IMPLEMENTED
;
549 ////////////////////////////////////////////////////////////////////////////////
551 ////////////////////////////////////////////////////////////////////////////////
553 nsresult
nsPropertyElement::Create(nsISupports
* aOuter
, REFNSIID aIID
,
556 return NS_ERROR_NO_AGGREGATION
;
558 RefPtr
<nsPropertyElement
> propElem
= new nsPropertyElement();
559 return propElem
->QueryInterface(aIID
, aResult
);
562 NS_IMPL_ISUPPORTS(nsPropertyElement
, nsIPropertyElement
)
565 nsPropertyElement::GetKey(nsACString
& aReturnKey
) {
571 nsPropertyElement::GetValue(nsAString
& aReturnValue
) {
572 aReturnValue
= mValue
;
577 nsPropertyElement::SetKey(const nsACString
& aKey
) {
583 nsPropertyElement::SetValue(const nsAString
& aValue
) {
588 ////////////////////////////////////////////////////////////////////////////////