Merge branch 'bugfix/label_range_safety' into 'release/10_2_0'

insert label range validations to prevent crashes

See merge request BPHV_MIPS/mvm_core_ui!881
This commit is contained in:
Pfeil, Scott Robert 2022-08-17 14:20:29 +00:00
commit 36e8be4de6
5 changed files with 74 additions and 7 deletions

View File

@ -326,7 +326,9 @@ public typealias ActionBlock = () -> ()
let attributedString = NSMutableAttributedString(string: labelText, attributes: [NSAttributedString.Key.font: font.updateSize(standardFontSize), NSAttributedString.Key.foregroundColor: textColor as UIColor])
for attribute in attributes {
let range = NSRange(location: attribute.location, length: attribute.length)
guard let range = validateAttribute(range: NSRange(location: attribute.location, length: attribute.length) , in: attributedString, type: attribute.type)
else { continue }
switch attribute {
case let underlineAtt as LabelAttributeUnderlineModel:
attributedString.addAttribute(.underlineStyle, value: underlineAtt.underlineValue.rawValue, range: range)
@ -458,10 +460,9 @@ public typealias ActionBlock = () -> ()
for case let attribute as [String: Any] in attributes {
guard let attributeType = attribute.optionalStringForKey(KeyType),
let location = attribute["location"] as? Int,
let length = attribute["length"] as? Int
else { continue }
let range = NSRange(location: location, length: length)
let length = attribute["length"] as? Int,
let range = validateAttribute(range: NSRange(location: location, length: length), in: attributedString, type: attributeType)
else { continue }
switch attributeType {
case "underline":
@ -833,8 +834,10 @@ extension Label {
private func addActionAttributes(range: NSRange, string: NSMutableAttributedString?) {
guard let string = string else { return }
guard let string = string,
let range = validateAttribute(range: range, in: string)
else { return }
string.addAttributes([NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue], range: range)
}
@ -993,3 +996,26 @@ extension Label {
return false
}
}
//------------------------------------------------------
// MARK: - Validations
//------------------------------------------------------
func validateAttribute(range: NSRange, in string: NSAttributedString, type: String = "") -> NSRange? {
guard range.location >= 0 && range.location <= string.length else {
if let loggingHandler = MVMCoreLoggingHandler.shared(), loggingHandler.responds(to: #selector(MVMCoreLoggingHandler.addError(toLog:))) {
loggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute starting location \(range.lowerBound) is out of bounds for '\(string.string)'. Attribute is discarded.", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!)
}
return nil
}
if type != "image" && range.upperBound > string.length {
let newRange = NSRange(location: range.location, length: string.length - range.location)
if let loggingHandler = MVMCoreLoggingHandler.shared(), loggingHandler.responds(to: #selector(MVMCoreLoggingHandler.addError(toLog:))) {
loggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, messageToLog: "Attribute ending location \(range.upperBound) is out of bounds for '\(string)'. Adjusting to \(newRange.upperBound).", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: "\(#file): \(#function)")!)
}
return newRange
}
return range
}

View File

@ -35,6 +35,16 @@ class LabelAttributeImageModel: LabelAttributeModel {
case name
case URL
}
//--------------------------------------------------
// MARK: - Validations
//--------------------------------------------------
public override func validateInRange(of text: String) throws {
guard location >= 0 && location <= text.count else {
throw MolecularError.validationError("Attribute starting location \(location) is out of bounds for '\(text)'.")
}
}
//--------------------------------------------------
// MARK: - Codec

View File

@ -44,6 +44,21 @@
case length
}
//--------------------------------------------------
// MARK: - Validations
//--------------------------------------------------
public func validateInRange(of text: String) throws {
// Prevent invalid starting locations.
guard location >= 0 && location <= text.count else {
throw MolecularError.validationError("Attribute starting location \(location) is out of bounds for '\(text)'.")
}
// Prevent lengths extending beyond the bounds of the string.
guard length + location <= text.count else {
throw MolecularError.validationError("Attribute length \(length) starting at \(location) is out of bounds for '\(text)'.")
}
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------

View File

@ -62,6 +62,16 @@
self.text = text
}
//--------------------------------------------------
// MARK: - Validations
//--------------------------------------------------
public func validate(_ attributes: [LabelAttributeModel]) throws {
for attribute in attributes {
try attribute.validateInRange(of: text)
}
}
//--------------------------------------------------
// MARK: - Codec
//--------------------------------------------------
@ -82,6 +92,11 @@
makeWholeViewClickable = try typeContainer.decodeIfPresent(Bool.self, forKey: .makeWholeViewClickable)
numberOfLines = try typeContainer.decodeIfPresent(Int.self, forKey: .numberOfLines)
shouldMaskRecordedView = try typeContainer.decodeIfPresent(Bool.self, forKey: .shouldMaskRecordedView) ?? false
// Later make protocol based validate outside of decoding?
if let attributes = attributes {
try validate(attributes)
}
}
open func encode(to encoder: Encoder) throws {

View File

@ -1,6 +1,7 @@
public enum MolecularError: Swift.Error {
case error(String)
case validationError(String)
case countImbalance(String)
}