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