| LEFT | RIGHT | 
|---|
|  | 1 /* | 
|  | 2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
|  | 3  * Copyright (C) 2006-2015 Eyeo GmbH | 
|  | 4  * | 
|  | 5  * Adblock Plus is free software: you can redistribute it and/or modify | 
|  | 6  * it under the terms of the GNU General Public License version 3 as | 
|  | 7  * published by the Free Software Foundation. | 
|  | 8  * | 
|  | 9  * Adblock Plus is distributed in the hope that it will be useful, | 
|  | 10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | 12  * GNU General Public License for more details. | 
|  | 13  * | 
|  | 14  * You should have received a copy of the GNU General Public License | 
|  | 15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
|  | 16  */ | 
|  | 17 | 
| 1 #include "PluginStdAfx.h" | 18 #include "PluginStdAfx.h" | 
| 2 | 19 | 
| 3 #include "PluginFilter.h" | 20 #include "PluginFilter.h" | 
| 4 #include "PluginSettings.h" | 21 #include "PluginSettings.h" | 
| 5 #include "PluginClient.h" | 22 #include "PluginClient.h" | 
| 6 #include "PluginClientFactory.h" | 23 #include "PluginClientFactory.h" | 
| 7 #include "PluginMutex.h" | 24 #include "PluginMutex.h" | 
| 8 #include "PluginSettings.h" | 25 #include "PluginSettings.h" | 
| 9 #include "PluginSystem.h" | 26 #include "PluginSystem.h" | 
| 10 #include "PluginClass.h" | 27 #include "PluginClass.h" | 
| 11 #include "mlang.h" | 28 #include "mlang.h" | 
| 12 | 29 | 
| 13 #include "..\shared\CriticalSection.h" | 30 #include "..\shared\CriticalSection.h" | 
| 14 #include "..\shared\Utils.h" | 31 #include "..\shared\Utils.h" | 
| 15 #include "..\shared\ContentType.h" |  | 
| 16 |  | 
| 17 | 32 | 
| 18 // The filters are described at http://adblockplus.org/en/filters | 33 // The filters are described at http://adblockplus.org/en/filters | 
| 19 | 34 | 
| 20 static CriticalSection s_criticalSectionFilterMap; | 35 static CriticalSection s_criticalSectionFilterMap; | 
| 21 | 36 | 
| 22 namespace | 37 namespace | 
| 23 { | 38 { | 
| 24   struct GetHtmlElementAttributeResult | 39   struct GetHtmlElementAttributeResult | 
| 25   { | 40   { | 
| 26     GetHtmlElementAttributeResult() : isAttributeFound(false) | 41     GetHtmlElementAttributeResult() : isAttributeFound(false) | 
| 27     { | 42     { | 
| 28     } | 43     } | 
| 29     std::wstring attributeValue; | 44     std::wstring attributeValue; | 
| 30     bool isAttributeFound; | 45     bool isAttributeFound; | 
| 31   }; | 46   }; | 
| 32 | 47 | 
| 33   bool GetHtmlElementAttribute(IHTMLElement* htmlElement, | 48   GetHtmlElementAttributeResult GetHtmlElementAttribute(IHTMLElement& htmlElemen
     t, | 
| 34     const ATL::CComBSTR& attributeName, GetHtmlElementAttributeResult& retValue) | 49     const ATL::CComBSTR& attributeName) | 
| 35   { | 50   { | 
| 36     if (!htmlElement) | 51     GetHtmlElementAttributeResult retValue; | 
| 37     { |  | 
| 38       return false; |  | 
| 39     } |  | 
| 40     ATL::CComVariant vAttr; | 52     ATL::CComVariant vAttr; | 
| 41     ATL::CComPtr<IHTMLElement4> htmlElement4; | 53     ATL::CComPtr<IHTMLElement4> htmlElement4; | 
| 42     if (FAILED(htmlElement->QueryInterface(&htmlElement4)) || !htmlElement4) | 54     if (FAILED(htmlElement.QueryInterface(&htmlElement4)) || !htmlElement4) | 
| 43     { | 55     { | 
| 44       return false; | 56       return retValue; | 
| 45     } | 57     } | 
| 46     ATL::CComPtr<IHTMLDOMAttribute> attributeNode; | 58     ATL::CComPtr<IHTMLDOMAttribute> attributeNode; | 
| 47     if (FAILED(htmlElement4->getAttributeNode(attributeName, &attributeNode)) ||
      !attributeNode) | 59     if (FAILED(htmlElement4->getAttributeNode(attributeName, &attributeNode)) ||
      !attributeNode) | 
| 48     { | 60     { | 
| 49       return false; | 61       return retValue; | 
| 50     } | 62     } | 
| 51     // we set that attribute found but it's not necessary that we can retrieve i
     ts value | 63     // we set that attribute found but it's not necessary that we can retrieve i
     ts value | 
| 52     retValue.isAttributeFound = true; | 64     retValue.isAttributeFound = true; | 
| 53     if (FAILED(attributeNode->get_nodeValue(&vAttr))) | 65     if (FAILED(attributeNode->get_nodeValue(&vAttr))) | 
| 54     { | 66     { | 
| 55       return false; | 67       return retValue; | 
| 56     } | 68     } | 
| 57     if (vAttr.vt == VT_BSTR && vAttr.bstrVal) | 69     if (vAttr.vt == VT_BSTR && vAttr.bstrVal) | 
| 58     { | 70     { | 
| 59       retValue.attributeValue = vAttr.bstrVal; | 71       retValue.attributeValue = vAttr.bstrVal; | 
| 60     } | 72     } | 
| 61     else if (vAttr.vt == VT_I4) | 73     else if (vAttr.vt == VT_I4) | 
| 62     { | 74     { | 
| 63       retValue.attributeValue = std::to_wstring(vAttr.iVal); | 75       retValue.attributeValue = std::to_wstring(vAttr.iVal); | 
| 64     } | 76     } | 
| 65     return true; | 77     return retValue; | 
| 66   } | 78   } | 
| 67 } | 79 } | 
| 68 | 80 | 
| 69 // ============================================================================ | 81 // ============================================================================ | 
| 70 // CFilterElementHideAttrSelector | 82 // CFilterElementHideAttrSelector | 
| 71 // ============================================================================ | 83 // ============================================================================ | 
| 72 | 84 | 
| 73 CFilterElementHideAttrSelector::CFilterElementHideAttrSelector() : m_type(TYPE_N
     ONE), m_pos(POS_NONE), m_bstrAttr(NULL) | 85 CFilterElementHideAttrSelector::CFilterElementHideAttrSelector() : m_type(TYPE_N
     ONE), m_pos(POS_NONE), m_bstrAttr(NULL) | 
| 74 { | 86 { | 
| 75 } | 87 } | 
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 252   m_predecessor = filter.m_predecessor; | 264   m_predecessor = filter.m_predecessor; | 
| 253 } | 265 } | 
| 254 | 266 | 
| 255 | 267 | 
| 256 // ============================================================================ | 268 // ============================================================================ | 
| 257 // CFilter | 269 // CFilter | 
| 258 // ============================================================================ | 270 // ============================================================================ | 
| 259 | 271 | 
| 260 CFilter::CFilter(const CFilter& filter) | 272 CFilter::CFilter(const CFilter& filter) | 
| 261 { | 273 { | 
| 262   m_contentType = filter.m_contentType; |  | 
| 263   m_filterType  = filter.m_filterType; | 274   m_filterType  = filter.m_filterType; | 
| 264 | 275 | 
| 265   m_isFirstParty = filter.m_isFirstParty; | 276   m_isFirstParty = filter.m_isFirstParty; | 
| 266   m_isThirdParty = filter.m_isThirdParty; | 277   m_isThirdParty = filter.m_isThirdParty; | 
| 267 | 278 | 
| 268   m_isMatchCase  = filter.m_isMatchCase; | 279   m_isMatchCase  = filter.m_isMatchCase; | 
| 269   m_isFromStart = filter.m_isFromStart; | 280   m_isFromStart = filter.m_isFromStart; | 
| 270   m_isFromEnd = filter.m_isFromEnd; | 281   m_isFromEnd = filter.m_isFromEnd; | 
| 271 | 282 | 
| 272   m_filterText = filter.m_filterText; | 283   m_filterText = filter.m_filterText; | 
| 273 | 284 | 
| 274   m_hitCount = filter.m_hitCount; | 285   m_hitCount = filter.m_hitCount; | 
| 275 } | 286 } | 
| 276 | 287 | 
| 277 | 288 | 
| 278 CFilter::CFilter() : m_isMatchCase(false), m_isFirstParty(false), | 289 CFilter::CFilter() : m_isMatchCase(false), m_isFirstParty(false), | 
| 279   m_isThirdParty(false), m_contentType(AdblockPlus::FilterEngine::ContentType::C
     ONTENT_TYPE_OTHER), | 290   m_isThirdParty(false), | 
| 280   m_isFromStart(false), m_isFromEnd(false), m_hitCount(0) | 291   m_isFromStart(false), m_isFromEnd(false), m_hitCount(0) | 
| 281 { | 292 { | 
| 282 } | 293 } | 
| 283 | 294 | 
| 284 | 295 | 
| 285 bool CFilterElementHide::IsMatchFilterElementHide(IHTMLElement* pEl) const | 296 bool CFilterElementHide::IsMatchFilterElementHide(IHTMLElement* pEl) const | 
| 286 { | 297 { | 
| 287   HRESULT hr; | 298   HRESULT hr; | 
| 288 | 299 | 
| 289   if (!m_tagId.IsEmpty()) | 300   if (!m_tagId.IsEmpty()) | 
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 364     { | 375     { | 
| 365       CComBSTR bstrId; | 376       CComBSTR bstrId; | 
| 366       if (SUCCEEDED(pEl->get_id(&bstrId)) && bstrId) | 377       if (SUCCEEDED(pEl->get_id(&bstrId)) && bstrId) | 
| 367       { | 378       { | 
| 368         value = bstrId; | 379         value = bstrId; | 
| 369         attrFound = true; | 380         attrFound = true; | 
| 370       } | 381       } | 
| 371     } | 382     } | 
| 372     else | 383     else | 
| 373     { | 384     { | 
| 374       GetHtmlElementAttributeResult attributeValue; | 385       auto attributeValue = GetHtmlElementAttribute(*pEl, attrIt->m_bstrAttr); | 
| 375       bool rc = GetHtmlElementAttribute(pEl, attrIt->m_bstrAttr, attributeValue)
     ; | 386       if (attrFound = attributeValue.isAttributeFound) | 
| 376       if (rc && (attrFound = attributeValue.isAttributeFound)) |  | 
| 377       { | 387       { | 
| 378         value = ToCString(attributeValue.attributeValue); | 388         value = ToCString(attributeValue.attributeValue); | 
| 379       } | 389       } | 
| 380     } | 390     } | 
| 381 | 391 | 
| 382     if (attrFound) | 392     if (attrFound) | 
| 383     { | 393     { | 
| 384       if (attrIt->m_pos == CFilterElementHideAttrPos::EXACT) | 394       if (attrIt->m_pos == CFilterElementHideAttrPos::EXACT) | 
| 385       { | 395       { | 
| 386         // TODO: IE rearranges the style attribute completely. Figure out if any
     thing can be done about it. | 396         // TODO: IE rearranges the style attribute completely. Figure out if any
     thing can be done about it. | 
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 524         } | 534         } | 
| 525       } | 535       } | 
| 526     } while (separatorChar != '\0'); | 536     } while (separatorChar != '\0'); | 
| 527   } | 537   } | 
| 528 | 538 | 
| 529   return true; | 539   return true; | 
| 530 } | 540 } | 
| 531 | 541 | 
| 532 bool CPluginFilter::IsElementHidden(const std::wstring& tag, IHTMLElement* pEl, 
     const std::wstring& domain, const std::wstring& indent) const | 542 bool CPluginFilter::IsElementHidden(const std::wstring& tag, IHTMLElement* pEl, 
     const std::wstring& domain, const std::wstring& indent) const | 
| 533 { | 543 { | 
| 534   CString tagCString = to_CString(tag); | 544   CString tagCString = ToCString(tag); | 
| 535 | 545 | 
| 536   CString id; | 546   CString id; | 
| 537   CComBSTR bstrId; | 547   CComBSTR bstrId; | 
| 538   if (SUCCEEDED(pEl->get_id(&bstrId)) && bstrId) | 548   if (SUCCEEDED(pEl->get_id(&bstrId)) && bstrId) | 
| 539   { | 549   { | 
| 540     id = bstrId; | 550     id = bstrId; | 
| 541   } | 551   } | 
| 542 | 552 | 
| 543   CString classNames; | 553   CString classNames; | 
| 544   CComBSTR bstrClassNames; | 554   CComBSTR bstrClassNames; | 
| 545   if (SUCCEEDED(pEl->get_className(&bstrClassNames)) && bstrClassNames) | 555   if (SUCCEEDED(pEl->get_className(&bstrClassNames)) && bstrClassNames) | 
| 546   { | 556   { | 
| 547     classNames = bstrClassNames; | 557     classNames = bstrClassNames; | 
| 548   } | 558   } | 
| 549 | 559 | 
| 550   CriticalSection::Lock filterEngineLock(s_criticalSectionFilterMap); | 560   CriticalSection::Lock filterEngineLock(s_criticalSectionFilterMap); | 
| 551   { | 561   { | 
| 552     // Search tag/id filters | 562     // Search tag/id filters | 
| 553     if (!id.IsEmpty()) | 563     if (!id.IsEmpty()) | 
| 554     { | 564     { | 
| 555       std::pair<TFilterElementHideTagsNamed::const_iterator, TFilterElementHideT
     agsNamed::const_iterator> idItEnum = | 565       std::pair<TFilterElementHideTagsNamed::const_iterator, TFilterElementHideT
     agsNamed::const_iterator> idItEnum = | 
| 556         m_elementHideTagsId.equal_range(std::make_pair(tagCString, id)); | 566         m_elementHideTagsId.equal_range(std::make_pair(tagCString, id)); | 
| 557       for (TFilterElementHideTagsNamed::const_iterator idIt = idItEnum.first; id
     It != idItEnum.second; idIt ++) | 567       for (TFilterElementHideTagsNamed::const_iterator idIt = idItEnum.first; id
     It != idItEnum.second; idIt ++) | 
| 558       { | 568       { | 
| 559         if (idIt->second.IsMatchFilterElementHide(pEl)) | 569         if (idIt->second.IsMatchFilterElementHide(pEl)) | 
| 560         { | 570         { | 
| 561 #ifdef ENABLE_DEBUG_RESULT | 571 #ifdef ENABLE_DEBUG_RESULT | 
| 562           DEBUG_HIDE_EL(indent + "HideEl::Found (tag/id) filter:" + idIt->second
     .m_filterText) | 572           DEBUG_HIDE_EL(indent + "HideEl::Found (tag/id) filter:" + idIt->second
     .m_filterText) | 
| 563             CPluginDebug::DebugResultHiding(tagCString, "id:" + id, idIt->second
     .m_filterText); | 573             CPluginDebug::DebugResultHiding(tagCString, L"id:" + id, idIt->secon
     d.m_filterText); | 
| 564 #endif | 574 #endif | 
| 565           return true; | 575           return true; | 
| 566         } | 576         } | 
| 567       } | 577       } | 
| 568 | 578 | 
| 569       // Search general id | 579       // Search general id | 
| 570       idItEnum = m_elementHideTagsId.equal_range(std::make_pair("", id)); | 580       idItEnum = m_elementHideTagsId.equal_range(std::make_pair("", id)); | 
| 571       for (TFilterElementHideTagsNamed::const_iterator idIt = idItEnum.first; id
     It != idItEnum.second; idIt ++) | 581       for (TFilterElementHideTagsNamed::const_iterator idIt = idItEnum.first; id
     It != idItEnum.second; idIt ++) | 
| 572       { | 582       { | 
| 573         if (idIt->second.IsMatchFilterElementHide(pEl)) | 583         if (idIt->second.IsMatchFilterElementHide(pEl)) | 
| 574         { | 584         { | 
| 575 #ifdef ENABLE_DEBUG_RESULT | 585 #ifdef ENABLE_DEBUG_RESULT | 
| 576           DEBUG_HIDE_EL(indent + "HideEl::Found (?/id) filter:" + idIt->second.m
     _filterText) | 586           DEBUG_HIDE_EL(indent + "HideEl::Found (?/id) filter:" + idIt->second.m
     _filterText) | 
| 577             CPluginDebug::DebugResultHiding(tagCString, "id:" + id, idIt->second
     .m_filterText); | 587             CPluginDebug::DebugResultHiding(tagCString, L"id:" + id, idIt->secon
     d.m_filterText); | 
| 578 #endif | 588 #endif | 
| 579           return true; | 589           return true; | 
| 580         } | 590         } | 
| 581       } | 591       } | 
| 582     } | 592     } | 
| 583 | 593 | 
| 584     // Search tag/className filters | 594     // Search tag/className filters | 
| 585     if (!classNames.IsEmpty()) | 595     if (!classNames.IsEmpty()) | 
| 586     { | 596     { | 
| 587       int pos = 0; | 597       int pos = 0; | 
| 588       CString className = classNames.Tokenize(L" \t\n\r", pos); | 598       CString className = classNames.Tokenize(L" \t\n\r", pos); | 
| 589       while (pos >= 0) | 599       while (pos >= 0) | 
| 590       { | 600       { | 
| 591         std::pair<TFilterElementHideTagsNamed::const_iterator, TFilterElementHid
     eTagsNamed::const_iterator> classItEnum = | 601         std::pair<TFilterElementHideTagsNamed::const_iterator, TFilterElementHid
     eTagsNamed::const_iterator> classItEnum = | 
| 592           m_elementHideTagsClass.equal_range(std::make_pair(tagCString, classNam
     e)); | 602           m_elementHideTagsClass.equal_range(std::make_pair(tagCString, classNam
     e)); | 
| 593 | 603 | 
| 594         for (TFilterElementHideTagsNamed::const_iterator classIt = classItEnum.f
     irst; classIt != classItEnum.second; ++classIt) | 604         for (TFilterElementHideTagsNamed::const_iterator classIt = classItEnum.f
     irst; classIt != classItEnum.second; ++classIt) | 
| 595         { | 605         { | 
| 596           if (classIt->second.IsMatchFilterElementHide(pEl)) | 606           if (classIt->second.IsMatchFilterElementHide(pEl)) | 
| 597           { | 607           { | 
| 598 #ifdef ENABLE_DEBUG_RESULT | 608 #ifdef ENABLE_DEBUG_RESULT | 
| 599             DEBUG_HIDE_EL(indent + "HideEl::Found (tag/class) filter:" + classIt
     ->second.m_filterText) | 609             DEBUG_HIDE_EL(indent + "HideEl::Found (tag/class) filter:" + classIt
     ->second.m_filterText) | 
| 600               CPluginDebug::DebugResultHiding(tagCString, "class:" + className, 
     classIt->second.m_filterText); | 610               CPluginDebug::DebugResultHiding(tagCString, L"class:" + className,
      classIt->second.m_filterText); | 
| 601 #endif | 611 #endif | 
| 602             return true; | 612             return true; | 
| 603           } | 613           } | 
| 604         } | 614         } | 
| 605 | 615 | 
| 606         // Search general class name | 616         // Search general class name | 
| 607         classItEnum = m_elementHideTagsClass.equal_range(std::make_pair("", clas
     sName)); | 617         classItEnum = m_elementHideTagsClass.equal_range(std::make_pair("", clas
     sName)); | 
| 608         for (TFilterElementHideTagsNamed::const_iterator classIt = classItEnum.f
     irst; classIt != classItEnum.second; ++ classIt) | 618         for (TFilterElementHideTagsNamed::const_iterator classIt = classItEnum.f
     irst; classIt != classItEnum.second; ++ classIt) | 
| 609         { | 619         { | 
| 610           if (classIt->second.IsMatchFilterElementHide(pEl)) | 620           if (classIt->second.IsMatchFilterElementHide(pEl)) | 
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 674       } | 684       } | 
| 675     } | 685     } | 
| 676   } | 686   } | 
| 677 | 687 | 
| 678   return isRead; | 688   return isRead; | 
| 679 } | 689 } | 
| 680 | 690 | 
| 681 void CPluginFilter::ClearFilters() | 691 void CPluginFilter::ClearFilters() | 
| 682 { | 692 { | 
| 683   // Clear filter maps | 693   // Clear filter maps | 
| 684   CriticalSection::Lock filterEngineLock(s_criticalSectionFilterMap); | 694   CriticalSection::Lock filterEngineLock(s_criticalSectionFilterMap); | 
| 685   { | 695   { | 
| 686     for (int i = 0; i < 2; i++) | 696     for (int i = 0; i < 2; i++) | 
| 687     { | 697     { | 
| 688       for (int j = 0; j < 2; j++) | 698       for (int j = 0; j < 2; j++) | 
| 689       { | 699       { | 
| 690         m_filterMap[i][j].clear(); | 700         m_filterMap[i][j].clear(); | 
| 691       } | 701       } | 
| 692       m_filterMapDefault[i].clear(); | 702       m_filterMapDefault[i].clear(); | 
| 693     } | 703     } | 
| 694 | 704 | 
| 695     m_elementHideTags.clear(); | 705     m_elementHideTags.clear(); | 
| 696     m_elementHideTagsId.clear(); | 706     m_elementHideTagsId.clear(); | 
| 697     m_elementHideTagsClass.clear(); | 707     m_elementHideTagsClass.clear(); | 
| 698   } | 708   } | 
| 699 } | 709 } | 
| 700 | 710 | 
| 701 bool CPluginFilter::ShouldBlock(const std::wstring& src, AdblockPlus::FilterEngi
     ne::ContentType contentType, const std::wstring& domain, bool addDebug) const | 711 bool CPluginFilter::ShouldBlock(const std::wstring& src, AdblockPlus::FilterEngi
     ne::ContentType contentType, const std::wstring& domain, bool addDebug) const | 
| 702 { | 712 { | 
| 703   CString srcCString = to_CString(src); | 713   std::wstring srcTrimmed = TrimString(src); | 
| 704 | 714 | 
| 705   // We should not block the empty string, so all filtering does not make sense | 715   // We should not block the empty string, so all filtering does not make sense | 
| 706   // Therefore we just return | 716   // Therefore we just return | 
| 707   if (srcCString.Trim().IsEmpty()) | 717   if (srcTrimmed.empty()) | 
| 708   { | 718   { | 
| 709     return false; | 719     return false; | 
| 710   } | 720   } | 
| 711 | 721 | 
| 712   CPluginSettings* settings = CPluginSettings::GetInstance(); | 722   CPluginSettings* settings = CPluginSettings::GetInstance(); | 
| 713 | 723 | 
| 714   CPluginClient* client = CPluginClient::GetInstance(); | 724   CPluginClient* client = CPluginClient::GetInstance(); | 
| 715   bool result = client->Matches(to_wstring(srcCString), contentType, domain); | 725   bool result = client->Matches(srcTrimmed, contentType, domain); | 
| 716 | 726 | 
| 717 #ifdef ENABLE_DEBUG_RESULT | 727 #ifdef ENABLE_DEBUG_RESULT | 
| 718   if (addDebug) | 728   if (addDebug) | 
| 719   { | 729   { | 
| 720     std::wstring type = ToUtf16String(ContentTypeToString(contentType)); | 730     std::wstring type = ToUtf16String(AdblockPlus::FilterEngine::ContentTypeToSt
     ring(contentType)); | 
| 721     if (result) | 731     if (result) | 
| 722     { | 732     { | 
| 723       DEBUG_FILTER("Filter::ShouldBlock " + type + " YES"); | 733       CPluginDebug::DebugResultBlocking(ToCString(type), srcTrimmed, domain); | 
| 724       CPluginDebug::DebugResultBlocking(to_CString(type), srcCString, domain); |  | 
| 725     } | 734     } | 
| 726     else | 735     else | 
| 727     { | 736     { | 
| 728       CPluginDebug::DebugResultIgnoring(to_CString(type), srcCString, domain); | 737       CPluginDebug::DebugResultIgnoring(ToCString(type), srcTrimmed, domain); | 
| 729     } | 738     } | 
| 730   } | 739   } | 
| 731 #endif | 740 #endif | 
| 732   return result; | 741   return result; | 
| 733 } | 742 } | 
| LEFT | RIGHT | 
|---|