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
13 #include "nsPersistentProperties.h"
14 #include "nsIProperties.h"
15
16 #include "mozilla/ArenaAllocatorExtensions.h"
17
18 using mozilla::ArenaStrdup;
19
20 struct PropertyTableEntry : public PLDHashEntryHdr {
21 // both of these are arena-allocated
22 const char* mKey;
23 const char16_t* mValue;
24 };
25
26 static const struct PLDHashTableOps property_HashTableOps = {
27 PLDHashTable::HashStringKey,
28 PLDHashTable::MatchStringKey,
29 PLDHashTable::MoveEntryStub,
30 PLDHashTable::ClearEntryStub,
31 nullptr,
32 };
33
34 //
35 // parser stuff
36 //
37 enum EParserState {
38 eParserState_AwaitingKey,
39 eParserState_Key,
40 eParserState_AwaitingValue,
41 eParserState_Value,
42 eParserState_Comment
43 };
44
45 enum EParserSpecial {
46 eParserSpecial_None, // not parsing a special character
47 eParserSpecial_Escaped, // awaiting a special character
48 eParserSpecial_Unicode // parsing a \Uxxx value
49 };
50
51 class MOZ_STACK_CLASS nsPropertiesParser {
52 public:
nsPropertiesParser(nsIPersistentProperties * aProps)53 explicit nsPropertiesParser(nsIPersistentProperties* aProps)
54 : mUnicodeValuesRead(0),
55 mUnicodeValue(u'\0'),
56 mHaveMultiLine(false),
57 mMultiLineCanSkipN(false),
58 mMinLength(0),
59 mState(eParserState_AwaitingKey),
60 mSpecialState(eParserSpecial_None),
61 mProps(aProps) {}
62
FinishValueState(nsAString & aOldValue)63 void FinishValueState(nsAString& aOldValue) {
64 static const char trimThese[] = " \t";
65 mKey.Trim(trimThese, false, true);
66
67 // This is really ugly hack but it should be fast
68 char16_t backup_char;
69 uint32_t minLength = mMinLength;
70 if (minLength) {
71 backup_char = mValue[minLength - 1];
72 mValue.SetCharAt('x', minLength - 1);
73 }
74 mValue.Trim(trimThese, false, true);
75 if (minLength) {
76 mValue.SetCharAt(backup_char, minLength - 1);
77 }
78
79 mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue);
80 mSpecialState = eParserSpecial_None;
81 WaitForKey();
82 }
83
GetState()84 EParserState GetState() { return mState; }
85
86 static nsresult SegmentWriter(nsIUnicharInputStream* aStream, void* aClosure,
87 const char16_t* aFromSegment,
88 uint32_t aToOffset, uint32_t aCount,
89 uint32_t* aWriteCount);
90
91 nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength);
92
93 private:
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
103
WaitForKey()104 void WaitForKey() { mState = eParserState_AwaitingKey; }
105
EnterKeyState()106 void EnterKeyState() {
107 mKey.Truncate();
108 mState = eParserState_Key;
109 }
110
WaitForValue()111 void WaitForValue() { mState = eParserState_AwaitingValue; }
112
EnterValueState()113 void EnterValueState() {
114 mValue.Truncate();
115 mMinLength = 0;
116 mState = eParserState_Value;
117 mSpecialState = eParserSpecial_None;
118 }
119
EnterCommentState()120 void EnterCommentState() { mState = eParserState_Comment; }
121
122 nsAutoString mKey;
123 nsAutoString mValue;
124
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:
129 // - "\\\r"
130 // - "\\\n"
131 // - "\\\r\n"
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
137 EParserState mState;
138 // if we see a '\' then we enter this special state
139 EParserSpecial mSpecialState;
140 nsCOMPtr<nsIPersistentProperties> mProps;
141 };
142
IsWhiteSpace(char16_t aChar)143 inline bool IsWhiteSpace(char16_t aChar) {
144 return (aChar == ' ') || (aChar == '\t') || (aChar == '\r') ||
145 (aChar == '\n');
146 }
147
IsEOL(char16_t aChar)148 inline bool IsEOL(char16_t aChar) { return (aChar == '\r') || (aChar == '\n'); }
149
ParseValueCharacter(char16_t aChar,const char16_t * aCur,const char16_t * & aTokenStart,nsAString & aOldValue)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:
157 switch (aChar) {
158 case '\\':
159 if (mHaveMultiLine) {
160 // there is nothing to append to mValue yet
161 mHaveMultiLine = false;
162 } else {
163 mValue += Substring(aTokenStart, aCur);
164 }
165
166 mSpecialState = eParserSpecial_Escaped;
167 break;
168
169 case '\n':
170 // if we detected multiline and got only "\\\r" ignore next "\n" if
171 // any
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
179 // buffer.
180 aTokenStart = aCur + 1;
181 break;
182 }
183 [[fallthrough]];
184
185 case '\r':
186 // we're done! We have a key and value
187 mValue += Substring(aTokenStart, aCur);
188 FinishValueState(aOldValue);
189 mHaveMultiLine = false;
190 break;
191
192 default:
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
203 // the buffer.
204 aTokenStart = aCur + 1;
205 break;
206 }
207 mHaveMultiLine = false;
208 aTokenStart = aCur;
209 }
210 break; // from switch on (aChar)
211 }
212 break; // from switch on (mSpecialState)
213
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;
220
221 switch (aChar) {
222 // the easy characters - \t, \n, and so forth
223 case 't':
224 mValue += char16_t('\t');
225 mMinLength = mValue.Length();
226 break;
227 case 'n':
228 mValue += char16_t('\n');
229 mMinLength = mValue.Length();
230 break;
231 case 'r':
232 mValue += char16_t('\r');
233 mMinLength = mValue.Length();
234 break;
235 case '\\':
236 mValue += char16_t('\\');
237 break;
238
239 // switch to unicode mode!
240 case 'u':
241 case 'U':
242 mSpecialState = eParserSpecial_Unicode;
243 mUnicodeValuesRead = 0;
244 mUnicodeValue = 0;
245 break;
246
247 // a \ immediately followed by a newline means we're going multiline
248 case '\r':
249 case '\n':
250 mHaveMultiLine = true;
251 mMultiLineCanSkipN = (aChar == '\r');
252 mSpecialState = eParserSpecial_None;
253 break;
254
255 default:
256 // don't recognize the character, so just append it
257 mValue += aChar;
258 break;
259 }
260 break;
261
262 // we're in the middle of parsing a 4-character unicode value
263 // like \u5f39
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);
271 } else {
272 // non-hex character. Append what we have, and move on.
273 mValue += mUnicodeValue;
274 mMinLength = mValue.Length();
275 mSpecialState = eParserSpecial_None;
276
277 // leave aTokenStart at this unknown character, so it gets appended
278 aTokenStart = aCur;
279
280 // ensure parsing this non-hex character again
281 return false;
282 }
283
284 if (++mUnicodeValuesRead >= 4) {
285 aTokenStart = aCur + 1;
286 mSpecialState = eParserSpecial_None;
287 mValue += mUnicodeValue;
288 mMinLength = mValue.Length();
289 }
290
291 break;
292 }
293
294 return true;
295 }
296
SegmentWriter(nsIUnicharInputStream * aStream,void * aClosure,const char16_t * aFromSegment,uint32_t aToOffset,uint32_t aCount,uint32_t * aWriteCount)297 nsresult nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream,
298 void* aClosure,
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);
304
305 *aWriteCount = aCount;
306 return NS_OK;
307 }
308
ParseBuffer(const char16_t * aBuffer,uint32_t aBufferLength)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;
313
314 // points to the start/end of the current key or value
315 const char16_t* tokenStart = nullptr;
316
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;
321 }
322
323 nsAutoString oldValue;
324
325 while (cur != end) {
326 char16_t c = *cur;
327
328 switch (mState) {
329 case eParserState_AwaitingKey:
330 if (c == '#' || c == '!') {
331 EnterCommentState();
332 }
333
334 else if (!IsWhiteSpace(c)) {
335 // not a comment, not whitespace, we must have found a key!
336 EnterKeyState();
337 tokenStart = cur;
338 }
339 break;
340
341 case eParserState_Key:
342 if (c == '=' || c == ':') {
343 mKey += Substring(tokenStart, cur);
344 WaitForValue();
345 }
346 break;
347
348 case eParserState_AwaitingValue:
349 if (IsEOL(c)) {
350 // no value at all! mimic the normal value-ending
351 EnterValueState();
352 FinishValueState(oldValue);
353 }
354
355 // ignore white space leading up to the value
356 else if (!IsWhiteSpace(c)) {
357 tokenStart = cur;
358 EnterValueState();
359
360 // make sure to handle this first character
361 if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
362 cur++;
363 }
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
367 // processed again.
368 continue;
369 }
370 break;
371
372 case eParserState_Value:
373 if (ParseValueCharacter(c, cur, tokenStart, oldValue)) {
374 cur++;
375 }
376 // See few lines above for reason of doing this
377 continue;
378
379 case eParserState_Comment:
380 // stay in this state till we hit EOL
381 if (c == '\r' || c == '\n') {
382 WaitForKey();
383 }
384 break;
385 }
386
387 // finally, advance to the next character
388 cur++;
389 }
390
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);
396 }
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);
400 }
401
402 return NS_OK;
403 }
404
nsPersistentProperties()405 nsPersistentProperties::nsPersistentProperties()
406 : mIn(nullptr),
407 mTable(&property_HashTableOps, sizeof(PropertyTableEntry), 16),
408 mArena() {}
409
410 nsPersistentProperties::~nsPersistentProperties() = default;
411
SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const412 size_t nsPersistentProperties::SizeOfIncludingThis(
413 mozilla::MallocSizeOf aMallocSizeOf) const {
414 // The memory used by mTable is accounted for in mArena.
415 size_t n = 0;
416 n += mArena.SizeOfExcludingThis(aMallocSizeOf);
417 n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
418 return aMallocSizeOf(this) + n;
419 }
420
NS_IMPL_ISUPPORTS(nsPersistentProperties,nsIPersistentProperties,nsIProperties)421 NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties,
422 nsIProperties)
423
424 NS_IMETHODIMP
425 nsPersistentProperties::Load(nsIInputStream* aIn) {
426 nsresult rv = NS_NewUnicharInputStream(aIn, getter_AddRefs(mIn));
427
428 if (rv != NS_OK) {
429 NS_WARNING("Error creating UnicharInputStream");
430 return NS_ERROR_FAILURE;
431 }
432
433 nsPropertiesParser parser(this);
434
435 uint32_t nProcessed;
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)) &&
440 nProcessed != 0)
441 ;
442 mIn = nullptr;
443 if (NS_FAILED(rv)) {
444 return rv;
445 }
446
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);
452 }
453
454 return NS_OK;
455 }
456
457 NS_IMETHODIMP
SetStringProperty(const nsACString & aKey,const nsAString & aNewValue,nsAString & aOldValue)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()));
463
464 if (entry->mKey) {
465 aOldValue = entry->mValue;
466 NS_WARNING(
467 nsPrintfCString("the property %s already exists", flatKey.get()).get());
468 } else {
469 aOldValue.Truncate();
470 }
471
472 entry->mKey = ArenaStrdup(flatKey, mArena);
473 entry->mValue = ArenaStrdup(aNewValue, mArena);
474
475 return NS_OK;
476 }
477
478 NS_IMETHODIMP
Save(nsIOutputStream * aOut,const nsACString & aHeader)479 nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader) {
480 return NS_ERROR_NOT_IMPLEMENTED;
481 }
482
483 NS_IMETHODIMP
GetStringProperty(const nsACString & aKey,nsAString & aValue)484 nsPersistentProperties::GetStringProperty(const nsACString& aKey,
485 nsAString& aValue) {
486 const nsCString& flatKey = PromiseFlatCString(aKey);
487
488 auto entry = static_cast<PropertyTableEntry*>(mTable.Search(flatKey.get()));
489 if (!entry) {
490 return NS_ERROR_FAILURE;
491 }
492
493 aValue = entry->mValue;
494 return NS_OK;
495 }
496
497 NS_IMETHODIMP
Enumerate(nsISimpleEnumerator ** aResult)498 nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult) {
499 nsCOMArray<nsIPropertyElement> props;
500
501 // We know the necessary size; we can avoid growing it while adding elements
502 props.SetCapacity(mTable.EntryCount());
503
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());
507
508 RefPtr<nsPropertyElement> element = new nsPropertyElement(
509 nsDependentCString(entry->mKey), nsDependentString(entry->mValue));
510
511 if (!props.AppendObject(element)) {
512 return NS_ERROR_OUT_OF_MEMORY;
513 }
514 }
515
516 return NS_NewArrayEnumerator(aResult, props, NS_GET_IID(nsIPropertyElement));
517 }
518
519 ////////////////////////////////////////////////////////////////////////////////
520 // XXX Some day we'll unify the nsIPersistentProperties interface with
521 // nsIProperties, but until now...
522
523 NS_IMETHODIMP
Get(const char * aProp,const nsIID & aUUID,void ** aResult)524 nsPersistentProperties::Get(const char* aProp, const nsIID& aUUID,
525 void** aResult) {
526 return NS_ERROR_NOT_IMPLEMENTED;
527 }
528
529 NS_IMETHODIMP
Set(const char * aProp,nsISupports * value)530 nsPersistentProperties::Set(const char* aProp, nsISupports* value) {
531 return NS_ERROR_NOT_IMPLEMENTED;
532 }
533 NS_IMETHODIMP
Undefine(const char * aProp)534 nsPersistentProperties::Undefine(const char* aProp) {
535 return NS_ERROR_NOT_IMPLEMENTED;
536 }
537
538 NS_IMETHODIMP
Has(const char * aProp,bool * aResult)539 nsPersistentProperties::Has(const char* aProp, bool* aResult) {
540 *aResult = !!mTable.Search(aProp);
541 return NS_OK;
542 }
543
544 NS_IMETHODIMP
GetKeys(nsTArray<nsCString> & aKeys)545 nsPersistentProperties::GetKeys(nsTArray<nsCString>& aKeys) {
546 return NS_ERROR_NOT_IMPLEMENTED;
547 }
548
549 ////////////////////////////////////////////////////////////////////////////////
550 // PropertyElement
551 ////////////////////////////////////////////////////////////////////////////////
552
Create(nsISupports * aOuter,REFNSIID aIID,void ** aResult)553 nsresult nsPropertyElement::Create(nsISupports* aOuter, REFNSIID aIID,
554 void** aResult) {
555 if (aOuter) {
556 return NS_ERROR_NO_AGGREGATION;
557 }
558 RefPtr<nsPropertyElement> propElem = new nsPropertyElement();
559 return propElem->QueryInterface(aIID, aResult);
560 }
561
NS_IMPL_ISUPPORTS(nsPropertyElement,nsIPropertyElement)562 NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement)
563
564 NS_IMETHODIMP
565 nsPropertyElement::GetKey(nsACString& aReturnKey) {
566 aReturnKey = mKey;
567 return NS_OK;
568 }
569
570 NS_IMETHODIMP
GetValue(nsAString & aReturnValue)571 nsPropertyElement::GetValue(nsAString& aReturnValue) {
572 aReturnValue = mValue;
573 return NS_OK;
574 }
575
576 NS_IMETHODIMP
SetKey(const nsACString & aKey)577 nsPropertyElement::SetKey(const nsACString& aKey) {
578 mKey = aKey;
579 return NS_OK;
580 }
581
582 NS_IMETHODIMP
SetValue(const nsAString & aValue)583 nsPropertyElement::SetValue(const nsAString& aValue) {
584 mValue = aValue;
585 return NS_OK;
586 }
587
588 ////////////////////////////////////////////////////////////////////////////////
589