xmlread.pp:

+ New option TDOMParseOptions.DisallowDoctype - prohibits processing of the DTD (specs compliant,
  targeted for SOAP applications).
+ New option TDOMParseOptions.MaxChars - limits max document length, protects against entity
  expansion attacks and DoS by feeding in too long documents. Default value of 0 means no
  restrictions. Tested with internal and external general entities, TBD with parameter entities.
* Fixed calculation of URIs used to retrieve external entities, they should be evaluated at the
  point of entity declaration rather than at the point of resolving (which happens at the first
  inclusion). 
dom.pp:
* TDOMNode.SetReadOnly, calling Attributes was causing creation of TAttributeMap on every element.
  Fixed.

git-svn-id: trunk@13313 -
This commit is contained in:
sergei 2009-06-22 19:57:11 +00:00
parent c21d5765ac
commit 09867a1f6e
2 changed files with 62 additions and 18 deletions

View File

@ -1097,9 +1097,9 @@ begin
child.SetReadOnly(Value); child.SetReadOnly(Value);
child := child.NextSibling; child := child.NextSibling;
end; end;
attrs := Attributes; if HasAttributes then
if Assigned(attrs) then
begin begin
attrs := Attributes;
for I := 0 to attrs.Length-1 do for I := 0 to attrs.Length-1 do
attrs[I].SetReadOnly(Value); attrs[I].SetReadOnly(Value);
end; end;

View File

@ -66,6 +66,8 @@ type
FCDSectionsAsText: Boolean; FCDSectionsAsText: Boolean;
FResolveExternals: Boolean; FResolveExternals: Boolean;
FNamespaces: Boolean; FNamespaces: Boolean;
FDisallowDoctype: Boolean;
FMaxChars: Cardinal;
public public
property Validate: Boolean read FValidate write FValidate; property Validate: Boolean read FValidate write FValidate;
property PreserveWhitespace: Boolean read FPreserveWhitespace write FPreserveWhitespace; property PreserveWhitespace: Boolean read FPreserveWhitespace write FPreserveWhitespace;
@ -74,6 +76,8 @@ type
property CDSectionsAsText: Boolean read FCDSectionsAsText write FCDSectionsAsText; property CDSectionsAsText: Boolean read FCDSectionsAsText write FCDSectionsAsText;
property ResolveExternals: Boolean read FResolveExternals write FResolveExternals; property ResolveExternals: Boolean read FResolveExternals write FResolveExternals;
property Namespaces: Boolean read FNamespaces write FNamespaces; property Namespaces: Boolean read FNamespaces write FNamespaces;
property DisallowDoctype: Boolean read FDisallowDoctype write FDisallowDoctype;
property MaxChars: Cardinal read FMaxChars write FMaxChars;
end; end;
// NOTE: DOM 3 LS ACTION_TYPE enumeration starts at 1 // NOTE: DOM 3 LS ACTION_TYPE enumeration starts at 1
@ -162,7 +166,9 @@ type
FOnStack: Boolean; FOnStack: Boolean;
FBetweenDecls: Boolean; FBetweenDecls: Boolean;
FReplacementText: DOMString; FReplacementText: DOMString;
FURI: DOMString;
FStartLocation: TLocation; FStartLocation: TLocation;
FCharCount: Cardinal;
end; end;
PWideCharBuf = ^TWideCharBuf; PWideCharBuf = ^TWideCharBuf;
@ -186,6 +192,7 @@ type
FXML11Rules: Boolean; FXML11Rules: Boolean;
FSystemID: WideString; FSystemID: WideString;
FPublicID: WideString; FPublicID: WideString;
FCharCount: Cardinal;
function GetSystemID: WideString; function GetSystemID: WideString;
function GetPublicID: WideString; function GetPublicID: WideString;
protected protected
@ -355,6 +362,8 @@ type
FCDSectionsAsText: Boolean; FCDSectionsAsText: Boolean;
FResolveExternals: Boolean; FResolveExternals: Boolean;
FNamespaces: Boolean; FNamespaces: Boolean;
FDisallowDoctype: Boolean;
FMaxChars: Cardinal;
procedure RaiseExpectedQmark; procedure RaiseExpectedQmark;
procedure Initialize(ASource: TXMLCharSource); procedure Initialize(ASource: TXMLCharSource);
@ -373,6 +382,7 @@ type
procedure CallErrorHandler(E: EXMLReadError); procedure CallErrorHandler(E: EXMLReadError);
function FindOrCreateElDef: TDOMElementDef; function FindOrCreateElDef: TDOMElementDef;
function SkipUntilSeq(const Delim: TSetOfChar; const More: array of WideChar): Boolean; function SkipUntilSeq(const Delim: TSetOfChar; const More: array of WideChar): Boolean;
procedure CheckMaxChars;
protected protected
FCursor: TDOMNode_WithChildren; FCursor: TDOMNode_WithChildren;
FNesting: Integer; FNesting: Integer;
@ -424,7 +434,7 @@ type
procedure ExpectChoiceOrSeq(CP: TContentParticle); procedure ExpectChoiceOrSeq(CP: TContentParticle);
procedure ParseElementDecl; procedure ParseElementDecl;
procedure ParseNotationDecl; procedure ParseNotationDecl;
function ResolveEntity(const SystemID, PublicID: WideString; out Source: TXMLCharSource): Boolean; function ResolveEntity(const AbsSysID, PublicID: WideString; out Source: TXMLCharSource): Boolean;
procedure ProcessDefaultAttributes(Element: TDOMElement; Map: TDOMNamedNodeMap); procedure ProcessDefaultAttributes(Element: TDOMElement; Map: TDOMNamedNodeMap);
procedure ProcessNamespaceAtts(Element: TDOMElement); procedure ProcessNamespaceAtts(Element: TDOMElement);
procedure AddBinding(Attr: TDOMAttr; Prefix: PHashItem; var Chain: TBinding); procedure AddBinding(Attr: TDOMAttr; Prefix: PHashItem; var Chain: TBinding);
@ -796,6 +806,7 @@ begin
FBuf := PWideChar(AData); FBuf := PWideChar(AData);
FBufEnd := FBuf + Length(AData); FBufEnd := FBuf + Length(AData);
LFPos := FBuf-1; LFPos := FBuf-1;
FCharCount := Length(AData);
end; end;
procedure TXMLCharSource.Initialize; procedure TXMLCharSource.Initialize;
@ -948,7 +959,12 @@ begin
if rslt = 0 then if rslt = 0 then
Break Break
else if rslt < 0 then else if rslt < 0 then
DecodingError('Invalid character in input stream'); DecodingError('Invalid character in input stream')
else
begin
Inc(FCharCount, rslt);
FReader.CheckMaxChars;
end;
until False; until False;
FBufEnd^ := #0; FBufEnd^ := #0;
@ -1153,20 +1169,14 @@ begin
Loc.LinePos := FSource.FBuf-FSource.LFPos; Loc.LinePos := FSource.FBuf-FSource.LFPos;
end; end;
function TXMLReader.ResolveEntity(const SystemID, PublicID: WideString; out Source: TXMLCharSource): Boolean; function TXMLReader.ResolveEntity(const AbsSysID, PublicID: WideString; out Source: TXMLCharSource): Boolean;
var var
AbsSysID: WideString;
Filename: string; Filename: string;
Stream: TStream; Stream: TStream;
fd: THandle; fd: THandle;
begin begin
Source := nil; Source := nil;
Result := False; Result := False;
if not Assigned(FSource) then
AbsSysID := SystemID
else
if not ResolveRelativeURI(FSource.SystemID, SystemID, AbsSysID) then
Exit;
{ TODO: alternative resolvers { TODO: alternative resolvers
These may be 'internal' resolvers or a handler set by application. These may be 'internal' resolvers or a handler set by application.
Internal resolvers should probably produce a TStream Internal resolvers should probably produce a TStream
@ -1256,6 +1266,23 @@ begin
E.Free; E.Free;
end; end;
procedure TXMLReader.CheckMaxChars;
var
src: TXMLCharSource;
total: Cardinal;
begin
if FMaxChars = 0 then
Exit;
src := FSource;
total := 0;
repeat
Inc(total, src.FCharCount);
if total > FMaxChars then
FatalError('Exceeded character count limit');
src := src.FParent;
until src = nil;
end;
procedure TXMLReader.CallErrorHandler(E: EXMLReadError); procedure TXMLReader.CallErrorHandler(E: EXMLReadError);
begin begin
try try
@ -1402,6 +1429,8 @@ begin
FIgnoreComments := FCtrl.Options.IgnoreComments; FIgnoreComments := FCtrl.Options.IgnoreComments;
FResolveExternals := FCtrl.Options.ResolveExternals; FResolveExternals := FCtrl.Options.ResolveExternals;
FNamespaces := FCtrl.Options.Namespaces; FNamespaces := FCtrl.Options.Namespaces;
FDisallowDoctype := FCtrl.Options.DisallowDoctype;
FMaxChars := FCtrl.Options.MaxChars;
end; end;
destructor TXMLReader.Destroy; destructor TXMLReader.Destroy;
@ -1696,7 +1725,7 @@ var
begin begin
if AEntity.SystemID <> '' then if AEntity.SystemID <> '' then
begin begin
Result := ResolveEntity(AEntity.SystemID, AEntity.PublicID, Src); Result := ResolveEntity(AEntity.FURI, AEntity.PublicID, Src);
if not Result then if not Result then
begin begin
// TODO: a detailed message like SysErrorMessage(GetLastError) would be great here // TODO: a detailed message like SysErrorMessage(GetLastError) would be great here
@ -1731,6 +1760,7 @@ begin
if Assigned(FSource.FEntity) then if Assigned(FSource.FEntity) then
begin begin
TDOMEntityEx(FSource.FEntity).FOnStack := False; TDOMEntityEx(FSource.FEntity).FOnStack := False;
TDOMEntityEx(FSource.FEntity).FCharCount := FSource.FCharCount;
// [28a] PE that was started between MarkupDecls may not end inside MarkupDecl // [28a] PE that was started between MarkupDecls may not end inside MarkupDecl
Error := TDOMEntityEx(FSource.FEntity).FBetweenDecls and FInsideDecl; Error := TDOMEntityEx(FSource.FEntity).FBetweenDecls and FInsideDecl;
end; end;
@ -1748,9 +1778,11 @@ var
RefName: WideString; RefName: WideString;
Child: TDOMNode; Child: TDOMNode;
SaveCursor: TDOMNode_WithChildren; SaveCursor: TDOMNode_WithChildren;
cnt: Cardinal;
begin begin
AEntity := nil; AEntity := nil;
SetString(RefName, FName.Buffer, FName.Length); SetString(RefName, FName.Buffer, FName.Length);
cnt := FName.Length+2;
if Assigned(FDocType) then if Assigned(FDocType) then
AEntity := FDocType.Entities.GetNamedItem(RefName) as TDOMEntityEx; AEntity := FDocType.Entities.GetNamedItem(RefName) as TDOMEntityEx;
@ -1758,19 +1790,19 @@ begin
if AEntity = nil then if AEntity = nil then
begin begin
if FStandalone or (FDocType = nil) or not (FHavePERefs or (FDocType.SystemID <> '')) then if FStandalone or (FDocType = nil) or not (FHavePERefs or (FDocType.SystemID <> '')) then
FatalError('Reference to undefined entity ''%s''', [RefName], FName.Length+2) FatalError('Reference to undefined entity ''%s''', [RefName], cnt)
else else
ValidationError('Undefined entity ''%s'' referenced', [RefName], FName.Length+2); ValidationError('Undefined entity ''%s'' referenced', [RefName], cnt);
FCursor.AppendChild(doc.CreateEntityReference(RefName)); FCursor.AppendChild(doc.CreateEntityReference(RefName));
Exit; Exit;
end; end;
if InAttr and (AEntity.SystemID <> '') then if InAttr and (AEntity.SystemID <> '') then
FatalError('External entity reference is not allowed in attribute value', FName.Length+2); FatalError('External entity reference is not allowed in attribute value', cnt);
if FStandalone and AEntity.FExternallyDeclared then if FStandalone and AEntity.FExternallyDeclared then
FatalError('Standalone constraint violation', FName.Length+2); FatalError('Standalone constraint violation', cnt);
if AEntity.NotationName <> '' then if AEntity.NotationName <> '' then
FatalError('Reference to unparsed entity ''%s''', [RefName], FName.Length+2); FatalError('Reference to unparsed entity ''%s''', [RefName], cnt);
if not AEntity.FResolved then if not AEntity.FResolved then
begin begin
@ -1796,6 +1828,9 @@ begin
end; end;
end; end;
end; end;
// charcount of the entity included is known at this point
Inc(FSource.FCharCount, AEntity.FCharCount - cnt);
CheckMaxChars;
if (not FExpandEntities) or (not AEntity.FResolved) then if (not FExpandEntities) or (not AEntity.FResolved) then
begin begin
// This will clone Entity children // This will clone Entity children
@ -2071,9 +2106,12 @@ end;
procedure TXMLReader.ParseDoctypeDecl; // [28] procedure TXMLReader.ParseDoctypeDecl; // [28]
var var
Src: TXMLCharSource; Src: TXMLCharSource;
DoctypeURI: WideString;
begin begin
if FState >= rsDTD then if FState >= rsDTD then
FatalError('Markup declaration is not allowed here'); FatalError('Markup declaration is not allowed here');
if FDisallowDoctype then
FatalError('Document type is prohibited by parser settings');
ExpectString('DOCTYPE'); ExpectString('DOCTYPE');
SkipS(True); SkipS(True);
@ -2111,7 +2149,8 @@ begin
if (FDocType.SystemID <> '') then if (FDocType.SystemID <> '') then
begin begin
if ResolveEntity(FDocType.SystemID, FDocType.PublicID, Src) then ResolveRelativeURI(FSource.SystemID, FDocType.SystemID, DoctypeURI);
if ResolveEntity(DocTypeURI, FDocType.PublicID, Src) then
begin begin
Initialize(Src); Initialize(Src);
try try
@ -2541,8 +2580,13 @@ begin
SetString(Entity.FReplacementText, FEntityValue.Buffer, FEntityValue.Length); SetString(Entity.FReplacementText, FEntityValue.Buffer, FEntityValue.Length);
end end
else else
begin
if not ParseExternalID(Entity.FSystemID, Entity.FPublicID, False) then if not ParseExternalID(Entity.FSystemID, Entity.FPublicID, False) then
FatalError('Expected entity value or external ID'); FatalError('Expected entity value or external ID');
{ need to resolve entity's SystemID relative to the current source,
which may differ from the source at the point of inclusion }
ResolveRelativeURI(FSource.SystemID, Entity.SystemID, Entity.FURI);
end;
if NDataAllowed then // [76] if NDataAllowed then // [76]
begin begin