Archived
Initial commit; substitutions can be reviewed, no further functionality implemented yet
This commit is contained in:
Generated
+150
@@ -0,0 +1,150 @@
|
||||
//
|
||||
// Cleaner.swift
|
||||
// SwiftSoup
|
||||
//
|
||||
// Created by Nabil Chatbi on 15/10/16.
|
||||
// Copyright © 2016 Nabil Chatbi.. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
open class Cleaner {
|
||||
fileprivate let whitelist: Whitelist
|
||||
|
||||
/**
|
||||
Create a new cleaner, that sanitizes documents using the supplied whitelist.
|
||||
@param whitelist white-list to clean with
|
||||
*/
|
||||
public init(_ whitelist: Whitelist) {
|
||||
self.whitelist = whitelist
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new, clean document, from the original dirty document, containing only elements allowed by the whitelist.
|
||||
The original document is not modified. Only elements from the dirt document's <code>body</code> are used.
|
||||
@param dirtyDocument Untrusted base document to clean.
|
||||
@return cleaned document.
|
||||
*/
|
||||
public func clean(_ dirtyDocument: Document)throws->Document {
|
||||
//Validate.notNull(dirtyDocument)
|
||||
let clean: Document = Document.createShell(dirtyDocument.getBaseUri())
|
||||
if (dirtyDocument.body() != nil && clean.body() != nil) // frameset documents won't have a body. the clean doc will have empty body.
|
||||
{
|
||||
try copySafeNodes(dirtyDocument.body()!, clean.body()!)
|
||||
}
|
||||
return clean
|
||||
}
|
||||
|
||||
/**
|
||||
Determines if the input document is valid, against the whitelist. It is considered valid if all the tags and attributes
|
||||
in the input HTML are allowed by the whitelist.
|
||||
<p>
|
||||
This method can be used as a validator for user input forms. An invalid document will still be cleaned successfully
|
||||
using the {@link #clean(Document)} document. If using as a validator, it is recommended to still clean the document
|
||||
to ensure enforced attributes are set correctly, and that the output is tidied.
|
||||
</p>
|
||||
@param dirtyDocument document to test
|
||||
@return true if no tags or attributes need to be removed; false if they do
|
||||
*/
|
||||
public func isValid(_ dirtyDocument: Document)throws->Bool {
|
||||
//Validate.notNull(dirtyDocument)
|
||||
let clean: Document = Document.createShell(dirtyDocument.getBaseUri())
|
||||
let numDiscarded: Int = try copySafeNodes(dirtyDocument.body()!, clean.body()!)
|
||||
return numDiscarded == 0
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
fileprivate func copySafeNodes(_ source: Element, _ dest: Element)throws->Int {
|
||||
let cleaningVisitor: Cleaner.CleaningVisitor = Cleaner.CleaningVisitor(source, dest, self)
|
||||
let traversor: NodeTraversor = NodeTraversor(cleaningVisitor)
|
||||
try traversor.traverse(source)
|
||||
return cleaningVisitor.numDiscarded
|
||||
}
|
||||
|
||||
fileprivate func createSafeElement(_ sourceEl: Element)throws->ElementMeta {
|
||||
let sourceTag: String = sourceEl.tagName()
|
||||
let destAttrs: Attributes = Attributes()
|
||||
let dest: Element = try Element(Tag.valueOf(sourceTag), sourceEl.getBaseUri(), destAttrs)
|
||||
var numDiscarded: Int = 0
|
||||
|
||||
if let sourceAttrs = sourceEl.getAttributes() {
|
||||
for sourceAttr: Attribute in sourceAttrs {
|
||||
if (try whitelist.isSafeAttribute(sourceTag, sourceEl, sourceAttr)) {
|
||||
destAttrs.put(attribute: sourceAttr)
|
||||
} else {
|
||||
numDiscarded+=1
|
||||
}
|
||||
}
|
||||
}
|
||||
let enforcedAttrs: Attributes = try whitelist.getEnforcedAttributes(sourceTag)
|
||||
destAttrs.addAll(incoming: enforcedAttrs)
|
||||
|
||||
return ElementMeta(dest, numDiscarded)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Cleaner {
|
||||
fileprivate final class CleaningVisitor: NodeVisitor {
|
||||
var numDiscarded: Int = 0
|
||||
let root: Element
|
||||
var destination: Element? // current element to append nodes to
|
||||
|
||||
private var cleaner: Cleaner
|
||||
|
||||
public init(_ root: Element, _ destination: Element, _ cleaner: Cleaner) {
|
||||
self.root = root
|
||||
self.destination = destination
|
||||
self.cleaner = cleaner
|
||||
}
|
||||
|
||||
public func head(_ source: Node, _ depth: Int)throws {
|
||||
if let sourceEl = (source as? Element) {
|
||||
if (cleaner.whitelist.isSafeTag(sourceEl.tagName())) { // safe, clone and copy safe attrs
|
||||
let meta: Cleaner.ElementMeta = try cleaner.createSafeElement(sourceEl)
|
||||
let destChild: Element = meta.el
|
||||
try destination?.appendChild(destChild)
|
||||
|
||||
numDiscarded += meta.numAttribsDiscarded
|
||||
destination = destChild
|
||||
} else if (source != root) { // not a safe tag, so don't add. don't count root against discarded.
|
||||
numDiscarded+=1
|
||||
}
|
||||
} else if let sourceText = (source as? TextNode) {
|
||||
let destText: TextNode = TextNode(sourceText.getWholeText(), source.getBaseUri())
|
||||
try destination?.appendChild(destText)
|
||||
} else if let sourceData = (source as? DataNode) {
|
||||
if sourceData.parent() != nil && cleaner.whitelist.isSafeTag(sourceData.parent()!.nodeName()) {
|
||||
//let sourceData: DataNode = (DataNode) source
|
||||
let destData: DataNode = DataNode(sourceData.getWholeData(), source.getBaseUri())
|
||||
try destination?.appendChild(destData)
|
||||
} else {
|
||||
numDiscarded+=1
|
||||
}
|
||||
} else { // else, we don't care about comments, xml proc instructions, etc
|
||||
numDiscarded+=1
|
||||
}
|
||||
}
|
||||
|
||||
public func tail(_ source: Node, _ depth: Int)throws {
|
||||
if let x = (source as? Element) {
|
||||
if cleaner.whitelist.isSafeTag(x.nodeName()) {
|
||||
// would have descended, so pop destination stack
|
||||
destination = destination?.parent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Cleaner {
|
||||
fileprivate struct ElementMeta {
|
||||
let el: Element
|
||||
let numAttribsDiscarded: Int
|
||||
|
||||
init(_ el: Element, _ numAttribsDiscarded: Int) {
|
||||
self.el = el
|
||||
self.numAttribsDiscarded = numAttribsDiscarded
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user