Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This library is divided into 7 parts, which are available as CocoaPods subspecs.
* **Container** - This subspec provides a simple `ContainerViewController` without any built-in navigation construct.
* **ActiveLabel** - This subspec provides a `UILabel` subclass that renders gradient "loading" animations while the label's `text` property is set to `nil`.
* **Obfuscation** - This subspec provides simple routines to remove plaintext passwords or keys from your source code.
* **LanguageFilter** - This subspec provides an extension on `String` that can check if a given string has potentially offensive language in it. It can also replace potentially offensive language using a provided character. The offensive word list is ROT13ed so that this language won't appear in your codebase directly.

## Usage

Expand Down Expand Up @@ -277,6 +278,20 @@ To use an obfuscated key in your code, create one and use the builder variables
let key = ObfuscatedKey().T.h.i.s.underscore.I.s.dash.o.b.f.u.s.c.a.t.e.d.value
```

### LanguageFilter

To find if a string contains potentially objectionable input, create a filter and place a query.

``` swift
// TODO

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we're missing the basic how-to from here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops!

I guess at least I put in the todo. I wrote that before I decided how I'd implement it. :)

```

To replace all instances of potentially objectionable input within a string, create a filter and request a substitution

``` swift
// TODO
```

## Example

To run the example project, clone the repo, open `UtiliKit.xcworkspace`, and run the "UtiliKit-iOSExample" project.
Expand Down
2 changes: 1 addition & 1 deletion Sources/UtiliKit/Container/ContainerViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ open class ContainerViewController: UIViewController {
}

open func addManagedChildIfNeeded(_ child: ManagedChild) {
if !managedChildren.contains { $0.viewController === child.viewController } {
if !managedChildren.contains(where: { $0.viewController === child.viewController }) {
managedChildren.insert(child, at: managedChildren.startIndex)
}

Expand Down
76 changes: 76 additions & 0 deletions Sources/UtiliKit/LanguageFilter/String+LanguageFilter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// String+LanguageFilter.swift
// UtiliKit-iOS
//
// Created by Russell Mirabelli on 6/19/20.
// Copyright © 2020 Bottle Rocket Studios. All rights reserved.
//

import Foundation

public extension String {

/// Performs rot13 (dumb) encoding on a `UnicodeScalar` to provide minimal
/// filtering of bad words.
/// - Parameter unicodeScalar: The character to be `rot13`'ed
/// - Returns: the `rot13` value
private func rot13(unicodeScalar: UnicodeScalar) -> Character {
var result = unicodeScalar.value

switch unicodeScalar {
case "A"..."M", "a"..."m":
result += 13
case "N"..."Z", "n"..."z":
result -= 13
default:
break
}

return Character(UnicodeScalar(result) ?? " ")
}

/// Encodes / decodes a `String` using rot13
/// - Parameter input: The `String` to be encoded / decoded
/// - Returns: The encoded or decoded `String`
private func rot13(input: String) -> String {
return String(input.unicodeScalars.map(rot13))
}

/// A list of words which could be found to be offensive. Stored in source
/// using rot13.
private var offensiveWords: [String] {
["nany", "nahf", "nefr", "nff", "onyyfnpx", "onyyf", "onfgneq", "ovgpu", "ovngpu", "oybbql",
"oybjwbo", "oybj wbo", "obyybpx", "obyybx", "obare", "obbo", "ohttre", "ohz", "ohgg", "ohggcyht",
"pyvgbevf", "pbpx", "pbba", "penc", "phag", "qnza", "qvpx", "qvyqb", "qlxr", "snt", "srpx",
"sryyngr", "sryyngvb", "srypuvat", "shpx", "s h p x", "shqtrcnpxre", "shqtr cnpxre", "synatr",
"Tbqqnza", "Tbq qnza", "uryy", "ubzb", "wrex", "wvmm", "xvxr", "xaboraq", "xabo raq", "ynovn",
"yznb", "yzsnb", "zhss", "avttre", "avttn", "bzt", "cravf", "cvff", "cbbc", "cevpx", "chor",
"chffl", "dhrre", "fpebghz", "frk", "fuvg", "f uvg", "fu1g", "fyhg", "fzrtzn", "fchax", "gvg",
"gbffre", "gheq", "gjng", "intvan", "jnax", "juber", "jgs"
].map { rot13(input: $0) }
}

/// Returns true if the `String` matches a set of potentially offensive
/// words.
var containsOffensiveLanguage: Bool {
!(offensiveWords.filter { self.contains($0) }.isEmpty)
}


/// Returns a `String` where potentially offensive language is replaced
/// with asterisks
var removingOffensiveLanguage: String {
self.replacingOffensiveWords(with: "*")
}

/// Replaces offensive words with the provided `String` (a single character is recommended)
/// - Parameter replacement: The `String` to use for replacement
/// - Returns: The string with offensive words replaced.
func replacingOffensiveWords(with substitute: String) -> String {
if !self.containsOffensiveLanguage { return self }
var replacement = self
for word in offensiveWords { replacement = replacement.replacingOccurrences(of: word, with: String(repeating: (substitute.first ?? "*"), count: word.count)) }
return replacement
}

}
61 changes: 61 additions & 0 deletions Tests/LanguageFilterTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// LanguageFilterTests.swift
// UtiliKit-iOSTests
//
// Created by Russell Mirabelli on 6/19/20.
// Copyright © 2020 Bottle Rocket Studios. All rights reserved.
//

import UtiliKit
import XCTest

class LanguageFilterTests: XCTestCase {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well this is certainly a... unique... set of tests 😄


func test_noOffensiveLanguageDoesNotReturnTrue() {
// arrange
let unoffensive = "angel"
// act
let containsOffensiveLanguage = unoffensive.containsOffensiveLanguage
// assert
XCTAssert(!containsOffensiveLanguage, "Should not trigger the offensive language filter")
}

func test_yesOffensiveLanguageDoesReturnTrue() {
// arrange
let offensive = "ass"
// act
let containsOffensiveLanguage = offensive.containsOffensiveLanguage
// assert
XCTAssert(containsOffensiveLanguage, "Should not trigger the offensive language filter")
}

func test_replacesWithCorrectLength() {
//arrange
let offensive = "bite my shiny metal ass"
// act
let replaced = offensive.removingOffensiveLanguage
let asterisks = replaced.filter { $0 == "*" }
// assert
XCTAssert(asterisks.count == 3, "Incorrect replacement length in \(replaced)")
}

func test_doesNotReplaceInoffensive() {
// arrange
let unoffensive = "my hovercraft is full of eels"
// act
let replaced = unoffensive.removingOffensiveLanguage
// assert
XCTAssert(!replaced.contains("*"), "Incorrect replacement in \(replaced)")
}

func test_replacesWithAlternateCharacter() {
//arrange
let offensive = "bite my shiny metal ass"
// act
let replaced = offensive.replacingOffensiveWords(with: "•")
let asterisks = replaced.filter { $0 == "•" }
// assert
XCTAssert(asterisks.count == 3, "Incorrect replacement length in \(replaced)")
}

}
24 changes: 19 additions & 5 deletions UtiliKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
0EFAFE9A21F81659005B2B16 /* ContainerViewControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFAFE9921F81659005B2B16 /* ContainerViewControllerTransitionCoordinator.swift */; };
0EFAFE9D21F81743005B2B16 /* ContainerTransitionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFAFE9C21F81743005B2B16 /* ContainerTransitionContext.swift */; };
0EFAFEA121FA0FA2005B2B16 /* ContainerPercentDrivenInteractiveTransitioner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EFAFEA021FA0FA2005B2B16 /* ContainerPercentDrivenInteractiveTransitioner.swift */; };
0EFAFEA221FBA89C005B2B16 /* UtiliKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8677313920601BB400C54343 /* UtiliKit.framework */; };
1D419DBD21235EE100B62D91 /* ManagedChild.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D419DBC21235EE100B62D91 /* ManagedChild.swift */; };
1D419DBF21236CC300B62D91 /* UtiliKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8677313920601BB400C54343 /* UtiliKit.framework */; };
5C574BF72264EC10003D1641 /* ActiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C574BF62264EC10003D1641 /* ActiveLabel.swift */; };
Expand All @@ -27,6 +26,8 @@
62F5256F2257F4FE0052B8E7 /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = 62F5256E2257F4FE0052B8E7 /* CHANGELOG.md */; };
7D5611B822E79AD40017008D /* ObfuscationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D5611B722E79AD40017008D /* ObfuscationTests.swift */; };
7D5611BA22E79B4C0017008D /* ObfuscatedKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D5611B922E79B4C0017008D /* ObfuscatedKey.swift */; };
7D97C3C7249CF11600CB0E21 /* String+LanguageFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D97C3C6249CF11600CB0E21 /* String+LanguageFilter.swift */; };
7D97C3C9249CF21100CB0E21 /* LanguageFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D97C3C8249CF21100CB0E21 /* LanguageFilterTests.swift */; };
8677314220601BB400C54343 /* UtiliKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8677313920601BB400C54343 /* UtiliKit.framework */; };
8677315720601D1A00C54343 /* UtiliKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 869826261FE87BFA0024B73D /* UtiliKitTests.swift */; };
8677315820601D1A00C54343 /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 869826271FE87BFA0024B73D /* DateTests.swift */; };
Expand Down Expand Up @@ -128,6 +129,8 @@
62F5256E2257F4FE0052B8E7 /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
7D5611B722E79AD40017008D /* ObfuscationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObfuscationTests.swift; sourceTree = "<group>"; };
7D5611B922E79B4C0017008D /* ObfuscatedKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObfuscatedKey.swift; sourceTree = "<group>"; };
7D97C3C6249CF11600CB0E21 /* String+LanguageFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+LanguageFilter.swift"; sourceTree = "<group>"; };
7D97C3C8249CF21100CB0E21 /* LanguageFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageFilterTests.swift; sourceTree = "<group>"; };
8677313920601BB400C54343 /* UtiliKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = UtiliKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8677314120601BB400C54343 /* UtiliKit-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UtiliKit-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
8677315A20601DD800C54343 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -209,7 +212,6 @@
buildActionMask = 2147483647;
files = (
1D419DBF21236CC300B62D91 /* UtiliKit.framework in Frameworks */,
0EFAFEA221FBA89C005B2B16 /* UtiliKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -284,6 +286,7 @@
children = (
869826271FE87BFA0024B73D /* DateTests.swift */,
7D5611B722E79AD40017008D /* ObfuscationTests.swift */,
7D97C3C8249CF21100CB0E21 /* LanguageFilterTests.swift */,
869826261FE87BFA0024B73D /* UtiliKitTests.swift */,
0ED9476220FD461300B642AB /* ContainerTests.swift */,
5C574BFA2264ED61003D1641 /* ActiveLabelTests.swift */,
Expand All @@ -300,6 +303,14 @@
path = Obfuscation;
sourceTree = "<group>";
};
7D97C3C5249CEF8900CB0E21 /* LanguageFilter */ = {
isa = PBXGroup;
children = (
7D97C3C6249CF11600CB0E21 /* String+LanguageFilter.swift */,
);
path = LanguageFilter;
sourceTree = "<group>";
};
8677315920601DD800C54343 /* Supporting Files */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -333,6 +344,7 @@
8698CFA220619EAD0065AE20 /* Container */,
8698CFA720619EAD0065AE20 /* General */,
8698CFAA20619EAD0065AE20 /* Instantiation */,
7D97C3C5249CEF8900CB0E21 /* LanguageFilter */,
7D5611B622E79AAE0017008D /* Obfuscation */,
8698CFB220619EAD0065AE20 /* TimelessDate */,
8698CFB520619EAD0065AE20 /* Version */,
Expand Down Expand Up @@ -556,7 +568,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1150;
ORGANIZATIONNAME = "Bottle Rocket Studios";
TargetAttributes = {
8677313820601BB400C54343 = {
Expand Down Expand Up @@ -648,6 +660,7 @@
8698CFC320619EAD0065AE20 /* StoryboardIdentifiable.swift in Sources */,
7D5611BA22E79B4C0017008D /* ObfuscatedKey.swift in Sources */,
8698CFC920619EAD0065AE20 /* Bundle+Extensions.swift in Sources */,
7D97C3C7249CF11600CB0E21 /* String+LanguageFilter.swift in Sources */,
8698CFC520619EAD0065AE20 /* UIStoryboard+Extensions.swift in Sources */,
8698CFC220619EAD0065AE20 /* ReuseIdentifiable.swift in Sources */,
8698CFC620619EAD0065AE20 /* UITableView+Extensions.swift in Sources */,
Expand All @@ -671,6 +684,7 @@
buildActionMask = 2147483647;
files = (
5C574BFB2264ED61003D1641 /* ActiveLabelTests.swift in Sources */,
7D97C3C9249CF21100CB0E21 /* LanguageFilterTests.swift in Sources */,
0ED9476420FD473700B642AB /* ContainerTests.swift in Sources */,
8677315720601D1A00C54343 /* UtiliKitTests.swift in Sources */,
8677315820601D1A00C54343 /* DateTests.swift in Sources */,
Expand Down Expand Up @@ -860,7 +874,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEBUG_INFORMATION_FORMAT = dwarf;
Expand Down Expand Up @@ -894,7 +908,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
Expand Down