home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / cmd / winfe / extgen.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  22.0 KB  |  574 lines

  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18.  
  19. /*  Author:
  20.  *      Garrett Arch Blythe
  21.  *      blythe@netscape.com
  22.  */
  23.  
  24. #include "stdafx.h"
  25. #include "extgen.h"
  26.  
  27. /*-----------------------------------------------------------------------**
  28. ** Things you do not care about, private internal implemenation details. **
  29. **-----------------------------------------------------------------------*/
  30. size_t ext_Extract(char *pOutExtension, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName);
  31. BOOL ext_ExtByIndex(int iIndex, char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName, const char *pMimeType);
  32. BOOL ext_PreserveExt(const char *pExt, DWORD dwFlags, const char *pOrigName);
  33. BOOL ext_ShellExecutable(const char *pExt, DWORD dwFlags);
  34. BOOL ext_PreserveMime(const char *pExt, const char *pMimeType);
  35. BOOL ext_ExtByMimeIndex(int iIndex, char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pMimeType);
  36. #ifdef XP_WIN32
  37. void ext_MimeDatabase(char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pMimeType);
  38. #endif
  39.  
  40. size_t EXT_Invent(char *pOutExtension, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName, const char *pMimeType)
  41. {
  42.     //  Ensure we have a buffer to provide consistent functionality if
  43.     //      the buffer is missing.
  44.     char aBuffer[_MAX_EXT];
  45.     if(!pOutExtension) {
  46.         pOutExtension = aBuffer;
  47.         stExtBufSize = sizeof(aBuffer);
  48.     }
  49.     
  50.     //  You may want to provide a big enough buffer to support extensions
  51.     //      on the native file system.
  52.     ASSERT(stExtBufSize >= _MAX_EXT);
  53.     
  54.     //  Limit in parameters.
  55.     //  We only allow the no period flag, and dot three flag,
  56.     //      as we manipulate the other flags herein.
  57.     ASSERT((dwFlags & (EXT_NO_PERIOD | EXT_DOT_THREE)) == dwFlags);
  58.     dwFlags &= (EXT_NO_PERIOD | EXT_DOT_THREE);
  59.     
  60.     //  Initialize out parameters.
  61.     size_t stReturns = 0;
  62.     *pOutExtension = '\0';
  63.     
  64.     //  We want to preserve the file extension, respect the MIME type,
  65.     //      and want the Shell to know how to handle the file.
  66.     //  This is absolutely the best scenario.
  67.     if(!stReturns) {
  68.         DWORD dwPassFlags = dwFlags |
  69.             EXT_PRESERVE_EXT |
  70.             EXT_PRESERVE_MIME |
  71.             EXT_SHELL_EXECUTABLE |
  72.             EXT_EXECUTABLE_IDENTITY;
  73.             
  74.         stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType);
  75.     }
  76.     
  77.     //  We want to respect MIME type over file extension, but we also
  78.     //      want the Shell to understand how to use the file.  Try to
  79.     //      find any extension under that MIME type which the Shell
  80.     //      will handle and use it.
  81.     if(!stReturns) {
  82.         DWORD dwPassFlags = dwFlags |
  83.             EXT_PRESERVE_MIME |
  84.             EXT_SHELL_EXECUTABLE |
  85.             EXT_EXECUTABLE_IDENTITY;
  86.             
  87.         stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType);
  88.     }
  89.     
  90.     //  We give up on respecting MIME type, and we just want the Shell
  91.     //      to be able to handle the file.  Is the current file
  92.     //      extension good enough?
  93.     if(!stReturns) {
  94.         DWORD dwPassFlags = dwFlags |
  95.             EXT_PRESERVE_EXT |
  96.             EXT_SHELL_EXECUTABLE |
  97.             EXT_EXECUTABLE_IDENTITY;
  98.             
  99.         stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType);
  100.     }
  101.     
  102.     //  Extension has no shell association.  Would be nice if the extension
  103.     //      were in the MIME list, however.
  104.     if(!stReturns) {
  105.         DWORD dwPassFlags = dwFlags |
  106.             EXT_PRESERVE_EXT |
  107.             EXT_PRESERVE_MIME;
  108.             
  109.         stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType);
  110.     }
  111.     
  112.     //  Extension has no shell association.  We want to respect
  113.     //      MIME type over filename, as we might want to know the MIME
  114.     //      type some time in the future.  See if we have an extension
  115.     //      for the MIME type.
  116.     if(!stReturns) {
  117.         DWORD dwPassFlags = dwFlags |
  118.             EXT_PRESERVE_MIME;
  119.             
  120.         stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType);
  121.     }
  122.     
  123.     //  Extension has no shell association.  We have no extension for the MIME type.
  124.     //  Simply give up and use the extension of the file if present.
  125.     if(!stReturns) {
  126.         DWORD dwPassFlags = dwFlags |
  127.             EXT_PRESERVE_EXT;
  128.             
  129.         stReturns = EXT_Generate(pOutExtension, stExtBufSize, dwPassFlags, pOrigName, pMimeType);
  130.     }
  131.     
  132.     //  All done.
  133.     return(stReturns);
  134. }    
  135.  
  136. size_t EXT_Generate(char *pOutExtension, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName, const char *pMimeType)
  137. {
  138.     //  Ensure we have a buffer to provide consistent functionality if
  139.     //      the buffer is missing.
  140.     char aBuffer[_MAX_EXT];
  141.     if(!pOutExtension) {
  142.         pOutExtension = aBuffer;
  143.         stExtBufSize = sizeof(aBuffer);
  144.     }
  145.     
  146.     //  You may want to provide a big enough buffer to support extensions
  147.     //      on the native file system.
  148.     ASSERT(stExtBufSize >= _MAX_EXT);
  149.     
  150.     //  Initialize out parameters.
  151.     *pOutExtension = '\0';
  152.     
  153.     //  Get the period business out of the way first.
  154.     char *pExt = pOutExtension;
  155.     if(!(dwFlags & EXT_NO_PERIOD) && (stExtBufSize > 1)) {
  156.         //  We use strcat so that the string is NULL terminated.
  157.         strcat(pExt, ".");
  158.         pExt++;
  159.     }
  160.     
  161.     //  We do not want to change the extension for a mime type of
  162.     //      application/octet-stream or applciation/x-msdownload.
  163.     //  In some cases, these files are binary and we used to miss
  164.     //      name them with .EXE extensions.
  165.     if(pMimeType) {
  166.         BOOL bPreserveExtension = FALSE;
  167.         if(0 == stricmp(APPLICATION_OCTET_STREAM, pMimeType)) {
  168.             bPreserveExtension = TRUE;
  169.         }
  170.         else if(0 == stricmp("application/x-msdownload", pMimeType)) {
  171.             bPreserveExtension = TRUE;
  172.         }
  173.         
  174.         if(bPreserveExtension) {
  175.             dwFlags |= EXT_PRESERVE_EXT;
  176.         }
  177.     }
  178.     
  179.     //  This loop traverses all extensions possible, and will verify
  180.     //      that the extension matches all flags before continuing.
  181.     BOOL bSuccess = FALSE;
  182.     for(int iIndex = 0; ext_ExtByIndex(iIndex, pExt, stExtBufSize - (pExt - pOutExtension), dwFlags, pOrigName, pMimeType); iIndex++) {
  183.         //  Do not check empty extensions.
  184.         if(!*pExt) {
  185.             continue;
  186.         }
  187.         
  188.         //  See if the extension holds up to the flags.
  189.         if((dwFlags & EXT_PRESERVE_EXT) && !ext_PreserveExt(pExt, dwFlags, pOrigName)) {
  190.             continue;
  191.         }
  192.         if((dwFlags & EXT_SHELL_EXECUTABLE) && !ext_ShellExecutable(pExt, dwFlags)) {
  193.             continue;
  194.         }
  195.         if((dwFlags & EXT_PRESERVE_MIME) && !ext_PreserveMime(pExt, pMimeType)) {
  196.             continue;
  197.         }
  198.         
  199.         //  Made it through all checks.
  200.         //  Extension is good.
  201.         bSuccess = TRUE;
  202.         break;
  203.     }
  204.     
  205.     //  Finally, if were going to fail, lets give caller
  206.     //      an empty buffer to work with (to help get around
  207.     //      those people that dont check error codes).
  208.     if(!bSuccess) {
  209.         *pOutExtension = '\0';
  210.     }
  211.     
  212.     //  All done.
  213.     return(strlen(pOutExtension));
  214. }
  215.  
  216. /*--------------------------------------------------------------------**
  217. ** Goal is to provide an abstract way to iterate through all possible **
  218. **     extensions these attributes can produce.                       **
  219. ** Caller must know to ignore empty strings if we return TRUE to      **
  220. **      avoid having intimate knowledge about what we do inside here. **
  221. **                                                                    **
  222. ** WIN32:  Offset will be greater since were doing one special        **
  223. **     step.                                                          **
  224. ** EXT_OFFSET if subtracted from iIndex will yield a zero index into  **
  225. **     another list.                                                  **
  226. **--------------------------------------------------------------------*/
  227. #ifdef XP_WIN32
  228. #define EXT_OFFSET 2
  229. #else
  230. #define EXT_OFFSET 1
  231. #endif
  232. BOOL ext_ExtByIndex(int iIndex, char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName, const char *pMimeType)
  233. {
  234.     //  We assume a few things in the internal implementation.
  235.     ASSERT(pExt);
  236.  
  237.     //  Set out parameters.
  238.     BOOL bReturns = FALSE;
  239.     *pExt = '\0';
  240.     
  241.     if(!iIndex) {
  242.         //  First extension should always be that of the file.
  243.         //  Always return TRUE, such that the following steps are
  244.         //      executed.
  245.         ext_Extract(pExt, stExtBufSize, dwFlags, pOrigName);
  246.         bReturns = TRUE;
  247.     }
  248. #ifdef XP_WIN32
  249.     else if (1 == iIndex) {
  250.         //  Check the MIME database.
  251.         //  Always return TRUE, such that the following steps are
  252.         //      executed.
  253.         ext_MimeDatabase(pExt, stExtBufSize, dwFlags, pMimeType);
  254.         bReturns = TRUE;
  255.     }
  256. #endif
  257.     else {
  258.         //  Iterate through extensions found by MIME type.
  259.         //  Notice that the index is correct on Win32 and
  260.         //      Win16 due to compile time manipulation
  261.         //      of the offset.
  262.         int iMimeIndex = iIndex - EXT_OFFSET;
  263.         bReturns = ext_ExtByMimeIndex(iMimeIndex, pExt, stExtBufSize, dwFlags, pMimeType);
  264.     }
  265.     
  266.     //  Return wether or not another extension is returned.
  267.     return(bReturns);
  268. }
  269. #undef EXT_OFFSET
  270.  
  271. /*-----------------------------------------------------------------**
  272. ** Goal is to specifically iterate our interally kept mime list(s) **
  273. ** Caller should know how to ignore empty strings with successful  **
  274. **      return value.
  275. **-----------------------------------------------------------------*/
  276. BOOL ext_ExtByMimeIndex(int iIndex, char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pMimeType)
  277. {
  278.     //  We assume a few things in the internal implementation.
  279.     ASSERT(pExt);
  280.     
  281.     //  Clear any out params.
  282.     BOOL bReturns = FALSE;
  283.     *pExt = '\0';
  284.     
  285.     //  No need to attempt if no Mime type.
  286.     if(pMimeType) {
  287.         //  Find the list of extensions of that MIME type.
  288.         XP_List *pTypesList = cinfo_MasterListPointer();
  289.         NET_cdataStruct *pListEntry = NULL;
  290.         while ((pListEntry = (NET_cdataStruct *)XP_ListNextObject(pTypesList)))    {
  291.             if(pListEntry->ci.type != NULL)    {
  292.                 if(!stricmp(pListEntry->ci.type, pMimeType)) {
  293.                     if(pListEntry->num_exts > iIndex) {
  294.                         //  We consider getting this far success.
  295.                         bReturns = TRUE;
  296.                         
  297.                         char *pFinally = pListEntry->exts[iIndex];
  298.                         if(pFinally) {
  299.                             if('.' == *pFinally) {
  300.                                 //  Go beyond the period.
  301.                                 pFinally++;
  302.                             }
  303.                             
  304.                             //  See if it matches the flags.
  305.                             BOOL bGood = FALSE;
  306.                             if(strlen(pFinally) < stExtBufSize) {
  307.                                 if(dwFlags & EXT_DOT_THREE) {
  308.                                     if(strlen(pFinally) <= 3) {
  309.                                         bGood = TRUE;
  310.                                     }
  311.                                 }
  312.                                 else {
  313.                                     bGood = TRUE;
  314.                                 }
  315.                             }
  316.                             
  317.                             if(bGood) {
  318.                                 strcpy(pExt, pFinally);
  319.                             }
  320.                         }
  321.                     }
  322.                     //  No need to continue loop after MIME type found.
  323.                     break;
  324.                 }
  325.             }
  326.         }
  327.     }
  328.     
  329.     return(bReturns);
  330. }
  331.  
  332. /*------------------------------------------------------------------**
  333. ** Goal is to determine wether or not the extension of the original **
  334. **     file is preserved in the extension passed in.                **
  335. **------------------------------------------------------------------*/
  336. BOOL ext_PreserveExt(const char *pExt, DWORD dwFlags, const char *pOrigName)
  337. {
  338.     BOOL bRetval = FALSE;
  339.  
  340.     if(pExt) {
  341.         char aBuffer[_MAX_EXT];
  342.         aBuffer[0] = '\0';
  343.         
  344.         if(pOrigName) {
  345.             //  Extract the extension.
  346.             //  Dont check return value.
  347.             //  Well want to return true if both strings are emptpy.
  348.             size_t stOrig = ext_Extract(aBuffer, sizeof(aBuffer), dwFlags, pOrigName);
  349.             if(!stOrig) {
  350.                 aBuffer[0] = '\0';
  351.             }
  352.         }
  353.         
  354.         if(!stricmp(aBuffer, pExt)) {
  355.             //  Good.
  356.             bRetval = TRUE;
  357.         }
  358.     }
  359.     
  360.     return(bRetval);
  361. }
  362.  
  363. /*-----------------------------------------------------------------**
  364. ** Goal is to determine wether or not the given extension is shell **
  365. **     executable.                                                 **
  366. **-----------------------------------------------------------------*/
  367. BOOL ext_ShellExecutable(const char *pExt, DWORD dwFlags)
  368. {
  369.     BOOL bRetval = FALSE;
  370.     
  371.     //  Empty strings fail.
  372.     if(pExt) {
  373.         //  Pass off to executable finder, which should just
  374.         //      rely on the shell to find the information with a
  375.         //      little extra logic.
  376.         //  We'll need to add a period.
  377.         char *pBuffer = (char *)XP_ALLOC(strlen(pExt) + 2);
  378.         if(pBuffer) {
  379.             strcpy(pBuffer, ".");
  380.             strcat(pBuffer, pExt);
  381.             bRetval = FEU_FindExecutable(pBuffer, NULL, dwFlags & EXT_EXECUTABLE_IDENTITY ? TRUE : FALSE, TRUE);
  382.             XP_FREE(pBuffer);
  383.             pBuffer = NULL;
  384.         }
  385.     }
  386.     
  387.     return(bRetval);
  388. }
  389.  
  390. /*----------------------------------------------------------------------**
  391. ** Goal is to determine wether or not the passed in extension correctly **
  392. **     represents the MIME type.                                        **
  393. **----------------------------------------------------------------------*/
  394. BOOL ext_PreserveMime(const char *pExt, const char *pMimeType)
  395. {
  396.     BOOL bRetval = FALSE;
  397.     
  398.     //  Need an extension and a MIME type to continue.
  399.     //  We need both so that we can make sure the extension is in
  400.     //      the mime type list.
  401.     if(pExt && pMimeType) {
  402.         //  Find the MIME type in our internal list.
  403.         //  We will not depend on any external information,
  404.         //      as all the inner workings are actually
  405.         //      controlled by our inner list, regardless of
  406.         //      how the system actually has things mapped out.
  407.         //  Find the list of extensions of that MIME type.
  408.         XP_List *pTypesList = cinfo_MasterListPointer();
  409.         NET_cdataStruct *pListEntry = NULL;
  410.         while ((pListEntry = (NET_cdataStruct *)XP_ListNextObject(pTypesList)))    {
  411.             if(pListEntry->ci.type != NULL)    {
  412.                 if(!stricmp(pListEntry->ci.type, pMimeType)) {
  413.                     //  See if extension is in the list.
  414.                     char *pListExt;
  415.                     for(int iNum = 0; iNum < pListEntry->num_exts; iNum++) {
  416.                         pListExt = pListEntry->exts[iNum];
  417.                         if('.' == *pListExt) {
  418.                             //  No need to compare with period.
  419.                             pListExt++;
  420.                         }
  421.                         
  422.                         //  Compare without case.
  423.                         if(!stricmp(pListExt, pExt)) {
  424.                             //  Mime type is represented by the extension.
  425.                             //  Preserved.
  426.                             bRetval = TRUE;
  427.                             break;
  428.                         }
  429.                     }
  430.                     //  No need to continue loop after MIME type found.
  431.                     break;
  432.                 }
  433.             }
  434.         }
  435.     }
  436.     
  437.     return(bRetval);
  438. }
  439.  
  440. /*-------------------------------------------------------------------------**
  441. ** Goal is to simply strip off the end of the filename without overflowing **
  442. **     the buffer.  We do not care about adding a period.                  **
  443. **-------------------------------------------------------------------------*/
  444. size_t ext_Extract(char *pOutExtension, size_t stExtBufSize, DWORD dwFlags, const char *pOrigName)
  445. {
  446.     size_t stReturns = 0;
  447.  
  448.     if(pOutExtension) {
  449.         if(pOrigName) {
  450.             //  Make a copy of the name we can mess with.
  451.             char *pMuck = XP_STRDUP(pOrigName);
  452.             if(pMuck) {
  453.                 //  Want to discard anyting beyond a '?' (get form post)
  454.                 //      or a '#' (named anchor).
  455.                 char *pDiscard;
  456.                 if(pDiscard = strchr(pMuck, '?')) {
  457.                     *pDiscard = '\0';
  458.                 }
  459.                 if(pDiscard = strchr(pMuck, '#')) {
  460.                     *pDiscard = '\0';
  461.                 }
  462.                 
  463.                 //  Find the trailing slash or backslash.
  464.                 char *pForeSlash = strrchr(pMuck, '/');
  465.                 char *pBackSlash = strrchr(pMuck, '\\');
  466.                 char *pSlash = pForeSlash > pBackSlash ? pForeSlash : pBackSlash;
  467.                 
  468.                 //  Find the trailing period.
  469.                 char *pPeriod = strrchr(pMuck, '.');
  470.                 
  471.                 //  Period must be present.
  472.                 //  Period must come after slash.
  473.                 if(pPeriod && (pPeriod > pSlash)) {
  474.                     //  Go one past the period.
  475.                     pPeriod++;
  476.                     
  477.                     //  Go over until end of buffer is reached,
  478.                     //      until we hit end of string, or
  479.                     //      until we hit a non-alpha-numeric character.
  480.                     //  We could support spaces and other valid fname
  481.                     //      chars, but we havent up to this point
  482.                     //      and see no need to further complicate the
  483.                     //      issues right now.
  484.                     while(*pPeriod && isalnum(*pPeriod) && (stExtBufSize - 1)) {
  485.                         *pOutExtension = *pPeriod;
  486.                         pPeriod++;
  487.                         pOutExtension++;
  488.                         stExtBufSize--;
  489.                         stReturns++;
  490.                         
  491.                         //  Consider 8.3
  492.                         if((dwFlags & EXT_DOT_THREE) && (stReturns >= 3)) {
  493.                             break;
  494.                         }
  495.                     }
  496.                 }
  497.                 XP_FREE(pMuck);
  498.                 pMuck = NULL;
  499.             }
  500.         }
  501.         //  End the out parameter (or sets to empty on failure).
  502.         ASSERT(stExtBufSize);
  503.         *pOutExtension = '\0';
  504.     }
  505.     
  506.     return(stReturns);
  507. }
  508.  
  509. #ifdef XP_WIN32
  510. /*------------------------------------------------------------------------**
  511. ** Goal is to look up the mime type and extension in the system registry. **
  512. **------------------------------------------------------------------------*/
  513. void ext_MimeDatabase(char *pExt, size_t stExtBufSize, DWORD dwFlags, const char *pMimeType)
  514. {
  515.     //  Clear out params.
  516.     ASSERT(pExt);
  517.     *pExt = '\0';
  518.  
  519.     if(pMimeType) {
  520.         //  Open up the MIME database.
  521.         HKEY hMime = NULL;
  522.         char aMime[_MAX_PATH];
  523.         strcpy(aMime, "MIME\\Database\\Content Type\\");
  524.         strcat(aMime, pMimeType);
  525.  
  526.         LONG lCheckOpen = RegOpenKeyEx(HKEY_CLASSES_ROOT, aMime, 0, KEY_QUERY_VALUE, &hMime);
  527.         if(ERROR_SUCCESS == lCheckOpen) {
  528.             //  Determine default extension.
  529.             char aExt[_MAX_EXT];
  530.             aExt[0] = '\0';
  531.             DWORD dwExtSize = sizeof(aExt);
  532.             LONG lCheckQuery = RegQueryValueEx(hMime, "Extension", NULL, NULL, (unsigned char *)aExt, &dwExtSize);
  533.             LONG lCheckClose = RegCloseKey(hMime);
  534.             ASSERT(ERROR_SUCCESS == lCheckClose);
  535.             hMime = NULL;
  536.             
  537.             if(ERROR_SUCCESS == lCheckQuery) {
  538.                 char *pEval = aExt;
  539.                 if('.' == *pEval) {
  540.                     //  Go beyond period, dont want it.
  541.                     pEval++;
  542.                 }
  543.                 
  544.                 if(*pEval) {
  545.                     BOOL bGood = FALSE;
  546.                     
  547.                     //  Got an extension.
  548.                     //  See if it is suitable.
  549.                     //  First, can it fit in our buffer?
  550.                     if(strlen(pEval) < stExtBufSize) {
  551.                         //  Check any relevant flags.
  552.                         if(dwFlags & EXT_DOT_THREE) {
  553.                             if(strlen(pEval) <= 3) {
  554.                                 bGood = TRUE;
  555.                             }
  556.                         }
  557.                         else {
  558.                             bGood = TRUE;
  559.                         }
  560.                     }
  561.                     
  562.                     if(bGood) {
  563.                         //  Good match.
  564.                         strcpy(pExt, pEval);
  565.                     }
  566.                 }
  567.             }
  568.         }
  569.     }
  570. }
  571. #endif
  572.  
  573.  
  574.