xpcom/ds/nsPersistentProperties.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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/. */
     6 #include "nsArrayEnumerator.h"
     7 #include "nsID.h"
     8 #include "nsCOMArray.h"
     9 #include "nsUnicharInputStream.h"
    10 #include "nsPrintfCString.h"
    11 #include "nsAutoPtr.h"
    13 #define PL_ARENA_CONST_ALIGN_MASK 3
    14 #include "nsPersistentProperties.h"
    15 #include "nsIProperties.h"
    17 struct PropertyTableEntry : public PLDHashEntryHdr
    18 {
    19   // both of these are arena-allocated
    20   const char *mKey;
    21   const char16_t *mValue;
    22 };
    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 }
    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 }
    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 };
    62 //
    63 // parser stuff
    64 //
    65 enum EParserState {
    66   eParserState_AwaitingKey,
    67   eParserState_Key,
    68   eParserState_AwaitingValue,
    69   eParserState_Value,
    70   eParserState_Comment
    71 };
    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 };
    79 class nsPropertiesParser
    80 {
    81 public:
    82   nsPropertiesParser(nsIPersistentProperties* aProps) :
    83     mHaveMultiLine(false), mState(eParserState_AwaitingKey),
    84     mProps(aProps) {}
    86   void FinishValueState(nsAString& aOldValue) {
    87     static const char trimThese[] = " \t";
    88     mKey.Trim(trimThese, false, true);
    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);
   102     mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue);
   103     mSpecialState = eParserSpecial_None;
   104     WaitForKey();
   105   }
   107   EParserState GetState() { return mState; }
   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);
   116   nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength);
   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
   129   void WaitForKey() {
   130     mState = eParserState_AwaitingKey;
   131   }
   133   void EnterKeyState() {
   134     mKey.Truncate();
   135     mState = eParserState_Key;
   136   }
   138   void WaitForValue() {
   139     mState = eParserState_AwaitingValue;
   140   }
   142   void EnterValueState() {
   143     mValue.Truncate();
   144     mMinLength = 0;
   145     mState = eParserState_Value;
   146     mSpecialState = eParserSpecial_None;
   147   }
   149   void EnterCommentState() {
   150     mState = eParserState_Comment;
   151   }
   153   nsAutoString mKey;
   154   nsAutoString mValue;
   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 };
   174 inline bool IsWhiteSpace(char16_t aChar)
   175 {
   176   return (aChar == ' ') || (aChar == '\t') ||
   177          (aChar == '\r') || (aChar == '\n');
   178 }
   180 inline bool IsEOL(char16_t aChar)
   181 {
   182   return (aChar == '\r') || (aChar == '\n');
   183 }
   186 bool nsPropertiesParser::ParseValueCharacter(
   187     char16_t c, const char16_t* cur, const char16_t* &tokenStart,
   188     nsAString& oldValue)
   189 {
   190   switch (mSpecialState) {
   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);
   202       mSpecialState = eParserSpecial_Escaped;
   203       break;
   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
   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;
   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)
   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;
   254     switch (c) {
   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;
   273       // switch to unicode mode!
   274     case 'u':
   275     case 'U':
   276       mSpecialState = eParserSpecial_Unicode;
   277       mUnicodeValuesRead = 0;
   278       mUnicodeValue = 0;
   279       break;
   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;
   289     default:
   290       // don't recognize the character, so just append it
   291       mValue += c;
   292       break;
   293     }
   294     break;
   296     // we're in the middle of parsing a 4-character unicode value
   297     // like \u5f39
   298   case eParserSpecial_Unicode:
   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;
   315       // leave tokenStart at this unknown character, so it gets appended
   316       tokenStart = cur;
   318       // ensure parsing this non-hex character again
   319       return false;
   320     }
   322     if (++mUnicodeValuesRead >= 4) {
   323       tokenStart = cur+1;
   324       mSpecialState = eParserSpecial_None;
   325       mValue += mUnicodeValue;
   326       mMinLength = mValue.Length();
   327     }
   329     break;
   330   }
   332   return true;
   333 }
   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);
   345   parser->ParseBuffer(aFromSegment, aCount);
   347   *aWriteCount = aCount;
   348   return NS_OK;
   349 }
   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;
   357   // points to the start/end of the current key or value
   358   const char16_t* tokenStart = nullptr;
   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   }
   367   nsAutoString oldValue;
   369   while (cur != end) {
   371     char16_t c = *cur;
   373     switch (mState) {
   374     case eParserState_AwaitingKey:
   375       if (c == '#' || c == '!')
   376         EnterCommentState();
   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;
   385     case eParserState_Key:
   386       if (c == '=' || c == ':') {
   387         mKey += Substring(tokenStart, cur);
   388         WaitForValue();
   389       }
   390       break;
   392     case eParserState_AwaitingValue:
   393       if (IsEOL(c)) {
   394         // no value at all! mimic the normal value-ending
   395         EnterValueState();
   396         FinishValueState(oldValue);
   397       }
   399       // ignore white space leading up to the value
   400       else if (!IsWhiteSpace(c)) {
   401         tokenStart = cur;
   402         EnterValueState();
   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;
   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;
   421     case eParserState_Comment:
   422       // stay in this state till we hit EOL
   423       if (c == '\r' || c== '\n')
   424         WaitForKey();
   425       break;
   426     }
   428     // finally, advance to the next character
   429     cur++;
   430   }
   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   }
   443   return NS_OK;
   444 }
   446 nsPersistentProperties::nsPersistentProperties()
   447 : mIn(nullptr)
   448 {
   449   mSubclass = static_cast<nsIPersistentProperties*>(this);
   451   PL_DHashTableInit(&mTable, &property_HashTableOps, nullptr,
   452                     sizeof(PropertyTableEntry), 20);
   454   PL_INIT_ARENA_POOL(&mArena, "PersistentPropertyArena", 2048);
   455 }
   457 nsPersistentProperties::~nsPersistentProperties()
   458 {
   459   PL_FinishArenaPool(&mArena);
   460   if (mTable.ops)
   461     PL_DHashTableFinish(&mTable);
   462 }
   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 }
   473 NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties, nsIProperties)
   475 NS_IMETHODIMP
   476 nsPersistentProperties::Load(nsIInputStream *aIn)
   477 {
   478   nsresult rv = nsSimpleUnicharStreamFactory::GetInstance()->
   479     CreateInstanceFromUTF8Stream(aIn, getter_AddRefs(mIn));
   481   if (rv != NS_OK) {
   482     NS_WARNING("Error creating UnicharInputStream");
   483     return NS_ERROR_FAILURE;
   484   }
   486   nsPropertiesParser parser(mSubclass);
   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;
   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   }
   504   return NS_OK;
   505 }
   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));
   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   }
   526   entry->mKey = ArenaStrdup(flatKey, &mArena);
   527   entry->mValue = ArenaStrdup(PromiseFlatString(aNewValue), &mArena);
   529   return NS_OK;
   530 }
   532 NS_IMETHODIMP
   533 nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader)
   534 {
   535   return NS_ERROR_NOT_IMPLEMENTED;
   536 }
   538 NS_IMETHODIMP
   539 nsPersistentProperties::Subclass(nsIPersistentProperties* aSubclass)
   540 {
   541   if (aSubclass) {
   542     mSubclass = aSubclass;
   543   }
   545   return NS_OK;
   546 }
   548 NS_IMETHODIMP
   549 nsPersistentProperties::GetStringProperty(const nsACString& aKey,
   550                                           nsAString& aValue)
   551 {
   552   const nsAFlatCString&  flatKey = PromiseFlatCString(aKey);
   554   PropertyTableEntry *entry =
   555     static_cast<PropertyTableEntry*>
   556                (PL_DHashTableOperate(&mTable, flatKey.get(), PL_DHASH_LOOKUP));
   558   if (PL_DHASH_ENTRY_IS_FREE(entry))
   559     return NS_ERROR_FAILURE;
   561   aValue = entry->mValue;
   562   return NS_OK;
   563 }
   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);
   574   nsPropertyElement *element =
   575     new nsPropertyElement(nsDependentCString(entry->mKey),
   576                           nsDependentString(entry->mValue));
   578   props->AppendObject(element);
   580   return PL_DHASH_NEXT;
   581 }
   584 NS_IMETHODIMP
   585 nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult)
   586 {
   587   nsCOMArray<nsIPropertyElement> props;
   589   // We know the necessary size; we can avoid growing it while adding elements
   590   props.SetCapacity(mTable.entryCount);
   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;
   598   return NS_NewArrayEnumerator(aResult, props);
   599 }
   601 ////////////////////////////////////////////////////////////////////////////////
   602 // XXX Some day we'll unify the nsIPersistentProperties interface with
   603 // nsIProperties, but until now...
   605 NS_IMETHODIMP
   606 nsPersistentProperties::Get(const char* prop, const nsIID & uuid, void* *result)
   607 {
   608   return NS_ERROR_NOT_IMPLEMENTED;
   609 }
   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 }
   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));
   629   *result = (entry && PL_DHASH_ENTRY_IS_BUSY(entry));
   631   return NS_OK;
   632 }
   634 NS_IMETHODIMP
   635 nsPersistentProperties::GetKeys(uint32_t *count, char ***keys)
   636 {
   637     return NS_ERROR_NOT_IMPLEMENTED;
   638 }
   640 ////////////////////////////////////////////////////////////////////////////////
   641 // PropertyElement
   642 ////////////////////////////////////////////////////////////////////////////////
   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 }
   653 NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement)
   655 NS_IMETHODIMP
   656 nsPropertyElement::GetKey(nsACString& aReturnKey)
   657 {
   658   aReturnKey = mKey;
   659   return NS_OK;
   660 }
   662 NS_IMETHODIMP
   663 nsPropertyElement::GetValue(nsAString& aReturnValue)
   664 {
   665   aReturnValue = mValue;
   666   return NS_OK;
   667 }
   669 NS_IMETHODIMP
   670 nsPropertyElement::SetKey(const nsACString& aKey)
   671 {
   672   mKey = aKey;
   673   return NS_OK;
   674 }
   676 NS_IMETHODIMP
   677 nsPropertyElement::SetValue(const nsAString& aValue)
   678 {
   679   mValue = aValue;
   680   return NS_OK;
   681 }
   683 ////////////////////////////////////////////////////////////////////////////////

mercurial