applayerpluginsandutils/httpprotocolplugins/httpheadercodec/chttpgeneralheaderreader.cpp
changeset 0 b16258d2340f
equal deleted inserted replaced
-1:000000000000 0:b16258d2340f
       
     1 // Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     2 // All rights reserved.
       
     3 // This component and the accompanying materials are made available
       
     4 // under the terms of "Eclipse Public License v1.0"
       
     5 // which accompanies this distribution, and is available
       
     6 // at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     7 //
       
     8 // Initial Contributors:
       
     9 // Nokia Corporation - initial contribution.
       
    10 //
       
    11 // Contributors:
       
    12 //
       
    13 // Description:
       
    14 //
       
    15 
       
    16 #include "chttpgeneralheaderreader.h"
       
    17 
       
    18 #include <http/rhttpsession.h>
       
    19 #include <httpstringconstants.h>
       
    20 #include <httperr.h>
       
    21 #include <inetprottextutils.h>
       
    22 
       
    23 _LIT8(KCommaSpaceSep,", ");
       
    24 _LIT8(KCommaNewline, ",\n");
       
    25 _LIT8(KEqualQuote, "=\"");
       
    26 
       
    27 CHttpGeneralHeaderReader* CHttpGeneralHeaderReader::NewL(RStringPool aStringPool)
       
    28 /** 
       
    29 	Factory constructor.
       
    30 	@internalComponent
       
    31 	@param		aStringPool	The current string pool.
       
    32 	@return		A pointer to a fully initialised object.
       
    33 	@leave		KErrNoMemory	Not enough memory to create object.
       
    34 */
       
    35 	{
       
    36 	return new(ELeave) CHttpGeneralHeaderReader(aStringPool);
       
    37 	}
       
    38 
       
    39 CHttpGeneralHeaderReader::~CHttpGeneralHeaderReader()
       
    40 /**
       
    41 	Destructor
       
    42 	@internalComponent
       
    43 */	{
       
    44 	}
       
    45 
       
    46 CHttpGeneralHeaderReader::CHttpGeneralHeaderReader(RStringPool aStringPool)
       
    47 : CHttpHeaderReader(aStringPool)
       
    48 /**
       
    49 	Constructor
       
    50 	@internalComponent
       
    51 	@param		aStringPool	The current string pool.
       
    52 */
       
    53 	{
       
    54 	}
       
    55 
       
    56 void CHttpGeneralHeaderReader::DecodeCacheControlL(RHeaderField& aHeader) const
       
    57 /**
       
    58 	Decodes a Cache-Control header. RFC 2616 section 14.9 - 
       
    59 
       
    60 		Cache-Control			=	"Cache-Control" ":" 1#cache-directive
       
    61 		cache-directive			=	cache-request-directive 
       
    62 								|	cache-response-directive
       
    63 		cache-request-directive	=	"no-cache" 
       
    64 								|	"no-store" 
       
    65 								|	"max-age" "=" delta-seconds
       
    66 								|	"max-stale" [ "=" delta-seconds ] 
       
    67 								|	"min-fresh" "=" delta-seconds 
       
    68 								|	"no-transform" 
       
    69 								|	"only-if-cached" 
       
    70 								|	cache-extension
       
    71 
       
    72 		cache-response-directive=	"public" 
       
    73 								|	"private" [ "=" <"> 1#field-name <"> ] 
       
    74 								|	"no-cache" [ "=" <"> 1#field-name <"> ]
       
    75 								|	"no-store" 
       
    76 								|	"no-transform"
       
    77 								|	"must-revalidate"
       
    78 								|	"proxy-revalidate"
       
    79 								|	"max-age" "=" delta-seconds
       
    80 								|	"s-maxage" "=" delta-seconds
       
    81 								|	cache-extension
       
    82 
       
    83 		cache-extension			=	token [ "=" ( token | quoted-string ) ]
       
    84 
       
    85 	The cache-control header value is a comma separated list of values with at
       
    86 	least one value.
       
    87 	@internalComponent
       
    88 	@param		aHeader	The cache-control header field to decode.
       
    89 	@leave		CHttpReader::DecodeGenericL
       
    90 	@todo		Is there any point in this? Why not call DecodeGenericL() straight away
       
    91 */
       
    92 	{
       
    93 	TPtrC8 rawData;
       
    94 	aHeader.RawDataL(rawData);
       
    95 	TInt remaining = rawData.Length();
       
    96 	TPtrC8 token;
       
    97 	TInt tokensFound =0;
       
    98 	RStringF maxAge = iStrPool.StringF(HTTP::EMaxAge, iStringTable);
       
    99 	RStringF maxStale = iStrPool.StringF(HTTP::EMaxStale, iStringTable);
       
   100 	RStringF minFresh = iStrPool.StringF(HTTP::EMinFresh, iStringTable);
       
   101 	RStringF smaxAge = iStrPool.StringF(HTTP::ESMaxAge, iStringTable);
       
   102 	while(remaining > 0)
       
   103 	    {
       
   104 		remaining -= InetProtTextUtils::ExtractNextTokenFromList(rawData, token, KCommaNewline());
       
   105 	
       
   106 
       
   107 		if(token.Locate('=') == KErrNotFound)
       
   108 		   {
       
   109 		  SetNewFStringPartL(aHeader, tokensFound, token);    
       
   110 		   }
       
   111 		else
       
   112 		   {
       
   113 		   CHeaderFieldPart*  part = SetNewFStringPartL(aHeader, tokensFound, KNullDesC8());
       
   114 		   TPtrC8 paramName;
       
   115 		   InetProtTextUtils::ExtractNextTokenFromList(token, paramName, '=');
       
   116 		   if ( (paramName.Compare(maxAge.DesC()) == 0) ||
       
   117 		   (paramName.Compare(maxStale.DesC()) == 0) ||
       
   118 		   (paramName.Compare(minFresh.DesC()) == 0)||
       
   119 		   (paramName.Compare(smaxAge.DesC()) == 0) )
       
   120 		       { 
       
   121 		       SetNewIntegerParamL(*part, paramName, token); 
       
   122 		       }
       
   123 		   else
       
   124 		       {
       
   125 		       SetNewFStringParamL(*part, paramName, token); 
       
   126 		       } 
       
   127 	       	   }
       
   128 	 ++tokensFound; 
       
   129 	    }
       
   130 	}
       
   131 		
       
   132 
       
   133 void CHttpGeneralHeaderReader::DecodeConnectionL(RHeaderField& aHeader) const
       
   134 /**
       
   135 	Decodes the Connection header. RFC2616 section 14.10 - 
       
   136 
       
   137 		Connection = "Connection" ":" 1#(connection-token)
       
   138 		connection-token  = token
       
   139 	The connection header value is a comma separated list of values with at least
       
   140 	one value.
       
   141 	@internalComponent
       
   142 	@param		aHeader	The connection header field to decode.
       
   143 	@leave		CHttpReader::DecodeGenericL
       
   144 	@todo		Is there any point in this? Why not call DecodeGenericL() straight away
       
   145 */
       
   146 	{
       
   147 	DecodeGenericL(aHeader, KCommaNewline);
       
   148 	}
       
   149 
       
   150 void CHttpGeneralHeaderReader::DecodeTransferEncodingL(RHeaderField& aHeader) const
       
   151 /**
       
   152 	Decodes the transfer-encoding header. RFC2616 section 14.41 -
       
   153 
       
   154 		Transfer-Encoding       =	"Transfer-Encoding" ":" 1#transfer-coding
       
   155 		transfer-coding         =	"chunked" | transfer-extension
       
   156 		transfer-extension      =	token *( ";" parameter )
       
   157 		parameter               =	attribute "=" value
       
   158 		attribute               =	token
       
   159 		value                   =	token | quoted-string
       
   160 
       
   161 	The transfer-encoding header is a comma separated list of values with at 
       
   162 	least one value.
       
   163 	@internalComponent
       
   164 	@param		aHeader	The transfer-encoding header field to decode.
       
   165 	@leave		RHeaderField::RawDataL
       
   166 	@leave		CHttpReader::SetNewFStringPartL
       
   167 	@leave		CHttpReader::SetNewFStringParamL
       
   168 	@leave		KErrHttpDecodeTransferEncoding	There was a transfer-extension
       
   169 												with no token before the 
       
   170 												parameter.
       
   171 */
       
   172 	{
       
   173 	TPtrC8 rawData;
       
   174 	aHeader.RawDataL(rawData);
       
   175 	TInt remaining = rawData.Length();
       
   176 	TInt xfrCodingsFound = 0;
       
   177 
       
   178 	// Iterate over the token list
       
   179 	while( remaining > 0 )
       
   180 		{
       
   181 		// locate a token and set the new header part at the appropriate position.
       
   182 		// Note - the token delimiter may be a '\n' or a ','
       
   183 		TPtrC8 xfrCoding;
       
   184 		remaining -= InetProtTextUtils::ExtractNextTokenFromList(rawData, xfrCoding, KCommaNewline);
       
   185 
       
   186 		// Search for ';' separator to determine if there is a parameter within 
       
   187 		// this transfer-coding. Don't bother check for 'chunked' per se, just 
       
   188 		// treat it as equivalent to a transfer-extension which means we would
       
   189 		// erroneously store parameters passed with 'chunked' (which the RFC doesn't allow)
       
   190 		TInt pos = xfrCoding.Locate(';');
       
   191 		if( pos == 0 )
       
   192 			{
       
   193 			// No transfer extension before the parameter pair!
       
   194 			User::Leave(KErrHttpDecodeTransferEncoding);
       
   195 			}
       
   196 		if( pos == KErrNotFound )
       
   197 			{
       
   198 			// A transfer encoding with no parameter. Trim surrounding whitespace.
       
   199 			InetProtTextUtils::RemoveWhiteSpace(xfrCoding, InetProtTextUtils::ERemoveBoth);
       
   200 
       
   201 			// Create and set the new header part at position zero
       
   202 			SetNewFStringPartL(aHeader, xfrCodingsFound, xfrCoding);
       
   203 			}
       
   204 		else
       
   205 			{
       
   206 			// Found the ';' separator - presumably a parameter/value pair.
       
   207 			TPtrC8 xfrExtn;
       
   208 			xfrExtn.Set(xfrCoding.Left(pos));
       
   209 			InetProtTextUtils::RemoveWhiteSpace(xfrExtn, InetProtTextUtils::ERemoveBoth);
       
   210 
       
   211 			// Set the transfer extension as a new header part
       
   212 			CHeaderFieldPart* part = SetNewFStringPartL(aHeader, xfrCodingsFound, xfrExtn);
       
   213 
       
   214 			// Get the param. Advance over the separator to the attrib-value pair.
       
   215 			xfrCoding.Set(xfrCoding.Mid(pos + 1));
       
   216 
       
   217 			// Search for '=' separator. If not found, then the param/value pair
       
   218 			// is badly formed - ignore for the sake of robustness.
       
   219 			pos = xfrCoding.Locate(TChar('='));
       
   220 			if( pos > 0 ) // shortest parameter name, 1 char
       
   221 				{
       
   222 				TPtrC8 parameter;
       
   223 				parameter.Set(xfrCoding.Left(pos));
       
   224 				InetProtTextUtils::RemoveWhiteSpace(parameter, InetProtTextUtils::ERemoveBoth);
       
   225 
       
   226 				TPtrC8 paramValue;
       
   227 				paramValue.Set(xfrCoding.Mid(pos + 1));
       
   228 				InetProtTextUtils::RemoveWhiteSpace(paramValue, InetProtTextUtils::ERemoveBoth);
       
   229 
       
   230 				// set the parameter of the header part
       
   231 				SetNewFStringParamL(*part, parameter, paramValue);
       
   232 				}
       
   233 			}
       
   234 		++xfrCodingsFound;
       
   235 		}
       
   236 	}
       
   237 
       
   238 void CHttpGeneralHeaderReader::DecodeContentTypeL(RHeaderField& aHeader) const
       
   239 /**
       
   240 	Decodes the content-type header. RFC2616 section 14.17
       
   241 
       
   242 		Content-Type			=	"Content-Type" ":" media-type
       
   243 		media-type				=	type "/" subtype *( ";" parameter )
       
   244 		type					=	token
       
   245 		subtype					=	token
       
   246 		parameter				=	attribute '=' value
       
   247 		attribute               =	token
       
   248 		value                   =	token | quoted-string
       
   249 	
       
   250 	The content-type header has a single media-type value. The atttribute 
       
   251 	is normally the 'charset'. E.g. Content-Type: text/html; charset=ISO-8859-4
       
   252 	@internalComponent
       
   253 	@param		aHeader	The content-type header field to decode.
       
   254 	@leave		RHeaderField::RawDataL
       
   255 	@leave		CHttpReader::SetNewFStringPartL
       
   256 	@leave		CHttpReader::SetNewFStringParamL
       
   257 	@leave		KErrHttpDecodeContentType	There was a transfer-extension with 
       
   258 											no token before the parameter.
       
   259 */
       
   260 	{
       
   261 	TPtrC8 rawData;
       
   262 	aHeader.RawDataL(rawData);
       
   263 	TInt remaining = rawData.Length();
       
   264 
       
   265 	// Search for '\n' separator. In the case when a duplicate header has been received,
       
   266 	// only use the first instance of the valid data.
       
   267 	TInt newLinePos = rawData.Locate('\n');
       
   268 	if (newLinePos != KErrNotFound)
       
   269 		{
       
   270 		rawData.Set(rawData.Left(newLinePos));
       
   271 		}
       
   272 
       
   273 	// Search for ';' separator
       
   274 	TInt pos = rawData.Locate(TChar(';'));
       
   275 	if( pos == 0 )
       
   276 		{
       
   277 		// No media type!
       
   278 		User::Leave(KErrHttpDecodeContentType);
       
   279 		}
       
   280 	if( pos == KErrNotFound )
       
   281 		{
       
   282 		// Media type only - no parameter. Trim surrounding whitespace.
       
   283 		InetProtTextUtils::RemoveWhiteSpace(rawData, InetProtTextUtils::ERemoveBoth);
       
   284 
       
   285 		// create and set the new header part at position zero
       
   286 		SetNewFStringPartL(aHeader, 0, rawData);
       
   287 		}
       
   288 	else
       
   289 		{
       
   290 		// found the separator; there is presumably a parameter/value pair
       
   291 		TPtrC8 mediaType;
       
   292 		mediaType.Set(rawData.Left(pos));
       
   293 		InetProtTextUtils::RemoveWhiteSpace(mediaType, InetProtTextUtils::ERemoveBoth);
       
   294 
       
   295 		// set the media type as a new header part
       
   296 		CHeaderFieldPart* part = SetNewFStringPartL(aHeader, 0, mediaType); // part 0, i.e. the first (and only) part
       
   297 
       
   298 		// get the param
       
   299 		remaining -= pos;
       
   300 		if (remaining <= 0)
       
   301 			User::Leave(KErrHttpDecodeContentType);
       
   302 		rawData.Set(rawData.Mid(pos + 1)); // move rawData onto the string after the ; char
       
   303 
       
   304 		TPtrC8 token;
       
   305 		while (remaining > 0)
       
   306 			{
       
   307 			remaining -= InetProtTextUtils::ExtractNextTokenFromList(rawData, token, ';');
       
   308 			if (!token.Length())
       
   309 				{
       
   310 				// no more tokens.
       
   311 				break;
       
   312 				}
       
   313 			SetParamNameAndValueL(*part, token, ETrue);
       
   314 			}
       
   315 		}
       
   316 	}
       
   317 
       
   318 void CHttpGeneralHeaderReader::DecodeContentDispositionL(RHeaderField& aHeader) const
       
   319 /**
       
   320 	Decodes the content-dispostion header. RFC2183 section 2
       
   321 
       
   322 		disposition				:=	"Content-Disposition" ":" 
       
   323 									disposition-type
       
   324 									*(";" disposition-parm)
       
   325 									
       
   326 		disposition-type		:=	"inline"
       
   327 									/ "attachment"
       
   328 									/ extension-token 
       
   329 									; values are not case-sensitive
       
   330 		
       
   331 		disposition-param		:=	filename-parm
       
   332                        				/ creation-date-parm
       
   333                        				/ modification-date-parm
       
   334                        				/ read-date-parm
       
   335                        				/ size-parm
       
   336                        				/ parameter
       
   337 		
       
   338 		filename-parm 			:= 	"filename" "=" value
       
   339 
       
   340      	creation-date-parm 		:= 	"creation-date" "=" quoted-date-time
       
   341 
       
   342      	modification-date-parm 	:= 	"modification-date" "=" quoted-date-time
       
   343 
       
   344      	read-date-parm 			:= 	"read-date" "=" quoted-date-time
       
   345 
       
   346      	size-parm 				:= 	"size" "=" 1*DIGIT
       
   347 
       
   348      	quoted-date-time 		:= 	quoted-string
       
   349                       				; contents MUST be an RFC 822 `date-time'
       
   350                       				; numeric timezones (+HHMM or -HHMM) MUST be used
       
   351 		
       
   352 	
       
   353 	@internalComponent
       
   354 	@param		aHeader	The content-disposition header field to decode.
       
   355 	@leave		RHeaderField::RawDataL
       
   356 	@leave		CHttpReader::SetNewFStringPartL
       
   357 	@leave		CHttpReader::SetNewFStringParamL
       
   358 	@leave		KErrHttpDecodeContentDisposition	There was a transfer-extension with 
       
   359 											no token before the parameter.
       
   360 */
       
   361 	{
       
   362 	TPtrC8 rawData;
       
   363 	aHeader.RawDataL(rawData);
       
   364 	TInt remaining = rawData.Length();
       
   365 
       
   366 	// Search for '\n' separator. In the case when a duplicate header has been received,
       
   367 	// only use the first instance of the valid data.
       
   368 	TInt newLinePos = rawData.Locate('\n');
       
   369 	if (newLinePos != KErrNotFound)
       
   370 		{
       
   371 		rawData.Set(rawData.Left(newLinePos));
       
   372 		}
       
   373 
       
   374 	// Search for ';' separator
       
   375 	TInt pos = rawData.Locate(TChar(';'));
       
   376 	if( pos == 0 )
       
   377 		{
       
   378 		// No disposition type!
       
   379 		User::Leave(KErrHttpDecodeContentDisposition);
       
   380 		}
       
   381 	if( pos == KErrNotFound )
       
   382 		{
       
   383 		// Disposition type only - no parameter. Trim surrounding whitespace.
       
   384 		InetProtTextUtils::RemoveWhiteSpace(rawData, InetProtTextUtils::ERemoveBoth);
       
   385 
       
   386 		// create and set the new header part at position zero
       
   387 		SetNewFStringPartL(aHeader, 0, rawData);
       
   388 		}
       
   389 	else
       
   390 		{
       
   391 		// found the separator; there is presumably a parameter/value pair
       
   392 		TPtrC8 mediaType;
       
   393 		mediaType.Set(rawData.Left(pos));
       
   394 		InetProtTextUtils::RemoveWhiteSpace(mediaType, InetProtTextUtils::ERemoveBoth);
       
   395 
       
   396 		// set the disposition type as a new header part
       
   397 		CHeaderFieldPart* part = SetNewFStringPartL(aHeader, 0, mediaType); // part 0, i.e. the first (and only) part
       
   398 
       
   399 		// get the param
       
   400 		remaining -= pos;
       
   401 		if (remaining <= 0)
       
   402 			{
       
   403 			User::Leave(KErrHttpDecodeContentDisposition); 
       
   404 			}
       
   405 		rawData.Set(rawData.Mid(pos + 1)); // move rawData onto the string after the ; char
       
   406 
       
   407 		while (remaining > 0)
       
   408 			{
       
   409 				if ( rawData.Find(KEqualQuote) != KErrNotFound )
       
   410 				{			     			     
       
   411 				TPtrC8 rawDataBuf;
       
   412 				rawDataBuf.Set(rawData);
       
   413 				
       
   414 				TPtrC8 param;		
       
   415 				InetProtTextUtils::ExtractNextTokenFromList(rawDataBuf, param, '=');
       
   416 				
       
   417 				TPtrC8 paramVal;					  
       
   418 				if(rawDataBuf.Locate('"') == 0)
       
   419 				{				   					 
       
   420 					// Go past the first '"' after the '='
       
   421 					rawDataBuf.Set(rawDataBuf.Mid(rawDataBuf.Locate('"')+1));
       
   422 					if ( rawDataBuf.Length() > 0)  
       
   423 					{
       
   424 						InetProtTextUtils::ExtractNextTokenFromList(rawDataBuf, paramVal, '"');
       
   425 					
       
   426 					rawData.Set(rawDataBuf);
       
   427 					if (rawData.Locate(';') != KErrNotFound)
       
   428 					{
       
   429 					// Go past the ';' character.
       
   430 					rawData.Set(rawData.Mid(1));
       
   431 					}
       
   432 					
       
   433 					SetNewFStringParamL(*part, param, paramVal);
       
   434 					}
       
   435 					else
       
   436 					{
       
   437 					// No value for the param, so set an empty param.
       
   438 					SetNewFStringParamL(*part, param, KNullDesC8());	
       
   439 					}
       
   440 				}
       
   441 				else
       
   442 				{
       
   443 					InetProtTextUtils::ExtractNextTokenFromList(rawDataBuf, paramVal, ';');
       
   444 					rawData.Set(rawDataBuf);
       
   445 					SetNewFStringParamL(*part, param, paramVal);			    
       
   446 				}
       
   447 				}
       
   448 				else
       
   449 				{
       
   450 					TPtrC8 token;
       
   451 					remaining -= InetProtTextUtils::ExtractNextTokenFromList(rawData, token,';' );				
       
   452 					
       
   453 					if (!token.Length())
       
   454 					{
       
   455 					// no more tokens.
       
   456 					break;
       
   457 					}
       
   458 					SetParamNameAndValueL(*part, token, ETrue);
       
   459 				}
       
   460 			}			
       
   461 		}
       
   462 	}
       
   463 	
       
   464 /*
       
   465  *	Methods from CHeaderReader
       
   466  */
       
   467 
       
   468 void CHttpGeneralHeaderReader::DecodeHeaderL(RHeaderField& aHeader)
       
   469 /** 
       
   470 	Decodes the raw header field value.
       
   471 	@internalComponent
       
   472 	@param		aHeader		The header field to decode.
       
   473 	@leave		CHttpGeneralHeaderReader::DecodeCacheControlL
       
   474 	@leave		CHttpGeneralHeaderReader::DecodeConnectionL
       
   475 	@leave		CHttpGeneralHeaderReader::DecodeContentTypeL
       
   476 	@leave		CHttpReader::DecodeDateL
       
   477 	@leave		CHttpReader::DecodeGenericL
       
   478 	@leave		CHttpReader::DecodeGenericNumberL
       
   479 	@leave		KErrNotSupported	The reader was asked to decode a header that
       
   480 									is does not support.
       
   481 */
       
   482 	{
       
   483 	RStringF fieldStr = iStrPool.StringF(aHeader.Name());
       
   484 	switch( fieldStr.Index(iStringTable) )
       
   485 		{
       
   486 	case HTTP::ECacheControl:
       
   487 		{
       
   488 		DecodeCacheControlL(aHeader);
       
   489 		} break;
       
   490 	case HTTP::EConnection:
       
   491 		{
       
   492 		DecodeConnectionL(aHeader);
       
   493 		} break;
       
   494 	case HTTP::EDate:
       
   495 	case HTTP::EExpires:
       
   496 		{
       
   497 		DecodeDateL(aHeader);
       
   498 		} break;
       
   499 	case HTTP::EPragma:
       
   500 	case HTTP::EContentLanguage:
       
   501 	case HTTP::EContentLocation: 	// Only expect one value
       
   502 	case HTTP::EContentMD5: 		// Only expect one value
       
   503 	case HTTP::EUpgrade:
       
   504 		{
       
   505 		DecodeGenericL(aHeader, KCommaNewline());
       
   506 		} break;
       
   507 	case HTTP::ETransferEncoding:
       
   508 		{
       
   509 		DecodeTransferEncodingL(aHeader);
       
   510 		} break;
       
   511 	case HTTP::EContentEncoding:
       
   512 		{
       
   513 		DecodeTokenListHeaderL(aHeader, KCommaSpaceSep());
       
   514 		} break;
       
   515 	case HTTP::EContentLength:
       
   516 		{
       
   517 		DecodeGenericNumberL(aHeader);
       
   518 		} break;
       
   519 	case HTTP::EContentType:
       
   520 		{
       
   521 		DecodeContentTypeL(aHeader);
       
   522 		} break;
       
   523 	case HTTP::EContentDisposition:
       
   524 		{
       
   525 		DecodeContentDispositionL(aHeader);	
       
   526 		} break;
       
   527 	default:
       
   528 		User::Leave(KErrNotSupported);
       
   529 		break;
       
   530 		}
       
   531 	}