Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package net.sjrx.intellij.plugins.systemdunitfiles.inspections

import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import net.sjrx.intellij.plugins.systemdunitfiles.intentions.AddPropertyAndValueQuickFix
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFilePropertyType
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileVisitor
import net.sjrx.intellij.plugins.systemdunitfiles.intentions.AddPropertyQuickFix
import java.util.*

/**
* This inspection warns when IPAddressAllow is specified without IPAddressDeny in certain sections,
* as this configuration does not block traffic (the default action is to permit).
*/
class IPAddressAllowOnlyInspection : LocalInspectionTool() {

// Sections where this inspection applies
private val targetSections = setOf("Slice", "Scope", "Service", "Socket")

override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : UnitFileVisitor() {
// Map to track properties by section
private val sectionProperties = mutableMapOf<String, MutableSet<String>>()

override fun visitFile(file: PsiFile) {
super.visitFile(file)

// After visiting the file, check each section for the condition
for ((section, properties) in sectionProperties) {
if (section in targetSections &&
"IPAddressAllow" in properties &&
"IPAddressDeny" !in properties) {

// Find all IPAddressAllow properties in this section to highlight
val sectionElement = findSectionElement(file, section)
if (sectionElement != null) {
val ipAddressAllowProperties = findPropertiesInSection(sectionElement, "IPAddressAllow")

for (property in ipAddressAllowProperties) {
holder.registerProblem(
property,
"Specifying IPAddressAllow without IPAddressDeny does not block traffic as the default action is to permit",
com.intellij.codeInspection.ProblemHighlightType.WEAK_WARNING,
AddPropertyAndValueQuickFix(section, "IPAddressDeny", "any")
)
}
}
}
}
}

override fun visitPropertyType(property: UnitFilePropertyType) {
super.visitPropertyType(property)

val section = PsiTreeUtil.getParentOfType(property, UnitFileSectionGroups::class.java)
if (section != null && section.sectionName in targetSections) {
// Add this property to the section's property set
val sectionName = section.sectionName
if (!sectionProperties.containsKey(sectionName)) {
sectionProperties[sectionName] = mutableSetOf()
}
sectionProperties[sectionName]?.add(property.key)
}
}

/**
* Find a section element by name in the file
*/
private fun findSectionElement(file: PsiFile, sectionName: String): UnitFileSectionGroups? {
val sections = PsiTreeUtil.findChildrenOfType(file, UnitFileSectionGroups::class.java)
return sections.find { it.sectionName == sectionName }
}

/**
* Find all properties with a specific key in a section
*/
private fun findPropertiesInSection(section: UnitFileSectionGroups, propertyKey: String): List<UnitFilePropertyType> {
val properties = PsiTreeUtil.findChildrenOfType(section, UnitFilePropertyType::class.java)
return properties.filter { it.key == propertyKey }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package net.sjrx.intellij.plugins.systemdunitfiles.intentions

import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.pom.Navigatable
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.childrenOfType
import com.intellij.psi.util.endOffset
import net.sjrx.intellij.plugins.systemdunitfiles.psi.UnitFileSectionGroups

class AddPropertyAndValueQuickFix(val section: String, val key: String, val value: String) : LocalQuickFix {
override fun getFamilyName(): String {
return "Add ${key}=${value} to ${section}"
}

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {

descriptor.psiElement.containingFile ?: return
val sectionGroup = PsiTreeUtil.getParentOfType(descriptor.psiElement, UnitFileSectionGroups::class.java) ?: return
val dummyFile = UnitElementFactory.createFile(project, sectionGroup.text + "\n${key}=${value}")

val newElement = sectionGroup.replace(dummyFile.firstChild)

(dummyFile.lastChild.navigationElement as? Navigatable)?.navigate(true)

FileEditorManager.getInstance(project).selectedTextEditor?.caretModel?.moveToOffset(newElement.endOffset)
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ class SemanticDataRepository private constructor() {
validatorMap.putAll(CPUSharesOptionValue.validators)
validatorMap.putAll(CgroupSocketBindOptionValue.validators)
validatorMap.putAll(RlimitOptionValue.validators) // Scopes are not supported since they aren't standard unit files.
validatorMap.putAll(NetworkAddressOptionValue.validators)
validatorMap.putAll(InAddrPrefixesOptionValue.validators)
fileClassToSectionNameToKeyValuesFromDoc["unit"]?.remove(SCOPE_KEYWORD)
fileClassToSectionToKeyAndValidatorMap["unit"]?.remove(SCOPE_KEYWORD)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.*

class InAddrPrefixesOptionValue(combinator: Combinator) : GrammarOptionValue("config_parse_in_addr_prefixes", combinator) {

companion object {

val validators = mapOf(
Validator("config_parse_in_addr_prefixes", "AF_UNSPEC") to InAddrPrefixesOptionValue(SequenceCombinator(IP_ADDR_PREFIX_LIST, EOF())),
Validator("config_parse_in_addr_prefixes", "AF_INET") to InAddrPrefixesOptionValue(SequenceCombinator(IPV4_ADDR_PREFIX_LIST, EOF())),
Validator("config_parse_in_addr_prefixes", "AF_INET6") to InAddrPrefixesOptionValue(SequenceCombinator(IPV6_ADDR_PREFIX_LIST, EOF()))
)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues

import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.Validator
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.EOF
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.GrammarOptionValue
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.IP_ADDR_AND_PREFIX_LENGTH
import net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.grammar.SequenceCombinator

class NetworkAddressOptionValue() : GrammarOptionValue("config_parse_address_section", GRAMMAR) {

companion object {
val GRAMMAR = SequenceCombinator(
IP_ADDR_AND_PREFIX_LENGTH, EOF())

val validators = mapOf(
Validator("config_parse_address_section", "ADDRESS_ADDRESS") to NetworkAddressOptionValue()
)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,21 @@ open class AlternativeCombinator(vararg val tokens: Combinator) : Combinator {
override fun SemanticMatch(value: String, offset: Int): MatchResult {
return match(value, offset, Combinator::SemanticMatch)
}

override fun toString(): String = toStringIndented(0)

override fun toStringIndented(indent: Int): String {
val prefix = " ".repeat(indent)
val sb = StringBuilder()
sb.append(prefix).append("Alt(\n")
for (token in tokens) {
if (token is SequenceCombinator || token is AlternativeCombinator || token is Repeat || token is ZeroOrOne || token is ZeroOrMore || token is OneOrMore) {
sb.append(token.toStringIndented(indent + 1)).append("\n")
} else {
sb.append(" ".repeat(indent + 1)).append(token.toString()).append("\n")
}
}
sb.append(prefix).append(")")
return sb.toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,5 @@ interface Combinator {
*/
fun SemanticMatch(value : String, offset: Int): MatchResult

fun toStringIndented(indent: Int): String
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,94 @@ package net.sjrx.intellij.plugins.systemdunitfiles.semanticdata.optionvalues.gra
val BYTES = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*")
val DEVICE = RegexTerminal("\\S+\\s*", "/[^\\u0000. ]+\\s*")
val IOPS = RegexTerminal("[0-9]+[a-zA-Z]*\\s*", "[0-9]+[KMGT]?\\s*")

var IPV4_OCTET = IntegerTerminal(0, 256)
val DOT = LiteralChoiceTerminal(".")
var IPV4_ADDR = SequenceCombinator(IPV4_OCTET, DOT, IPV4_OCTET, DOT, IPV4_OCTET, DOT, IPV4_OCTET)

val CIDR_SEPARATOR = LiteralChoiceTerminal("/")

val IPV4_ADDR_AND_PREFIX_LENGTH = SequenceCombinator(IPV4_ADDR, CIDR_SEPARATOR, IntegerTerminal(8, 33))
val IPV4_ADDR_AND_OPTIONAL_PREFIX_LENGTH = SequenceCombinator(IPV4_ADDR, ZeroOrOne(SequenceCombinator( CIDR_SEPARATOR, IntegerTerminal(8, 33))))

var IPV6_HEXTET = RegexTerminal("[0-9a-fA-F]{1,4}", "[0-9a-fA-F]{1,4}")
val COLON = LiteralChoiceTerminal(":")
val DOUBLE_COLON = LiteralChoiceTerminal("::")


val IPV6_FULL_SPECIFIED = SequenceCombinator(IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET)
val IPV6_ZERO_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(DOUBLE_COLON, ZeroOrOne(SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 8), IPV6_HEXTET)))
val IPV6_ONE_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, DOUBLE_COLON, ZeroOrOne(SequenceCombinator( Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 7), IPV6_HEXTET)))
val IPV6_TWO_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON, IPV6_HEXTET, DOUBLE_COLON, ZeroOrOne(SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 6), IPV6_HEXTET)))
val IPV6_THREE_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, DOUBLE_COLON, ZeroOrOne(SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 5), IPV6_HEXTET)))
val IPV6_FOUR_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, DOUBLE_COLON, ZeroOrOne(SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 4), IPV6_HEXTET)))
val IPV6_FIVE_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, DOUBLE_COLON, ZeroOrOne(SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 3), IPV6_HEXTET)))
val IPV6_SIX_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, DOUBLE_COLON, ZeroOrOne(SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 2), IPV6_HEXTET)))
val IPV6_SEVEN_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, COLON,IPV6_HEXTET, DOUBLE_COLON, ZeroOrOne(IPV6_HEXTET))


val IPV6_IPV4_SUFFIX_FULL = SequenceCombinator(IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV4_ADDR)
val IPV6_IPV4_SUFFIX_ZERO_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(DOUBLE_COLON,SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 6), IPV4_ADDR))
val IPV6_IPV4_SUFFIX_ONE_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, DOUBLE_COLON, SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 5), IPV4_ADDR))
val IPV6_IPV4_SUFFIX_TWO_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON, IPV6_HEXTET, DOUBLE_COLON, SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 4), IPV4_ADDR))
val IPV6_IPV4_SUFFIX_THREE_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, DOUBLE_COLON,SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 3), IPV4_ADDR))
val IPV6_IPV4_SUFFIX_FOUR_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, DOUBLE_COLON, SequenceCombinator(Repeat(SequenceCombinator(IPV6_HEXTET, COLON), 0, 2), IPV4_ADDR))
val IPV6_IPV4_SUFFIX_FIVE_HEXTET_BEFORE_ZERO_COMP = SequenceCombinator(IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, COLON, IPV6_HEXTET, DOUBLE_COLON, IPV4_ADDR)

//val IPV6_ALL_ZEROS = DOUBLE_COLON

val IPV6_ADDR = AlternativeCombinator(
IPV6_IPV4_SUFFIX_FULL,
IPV6_IPV4_SUFFIX_ZERO_HEXTET_BEFORE_ZERO_COMP,
IPV6_IPV4_SUFFIX_ONE_HEXTET_BEFORE_ZERO_COMP,
IPV6_IPV4_SUFFIX_TWO_HEXTET_BEFORE_ZERO_COMP,
IPV6_IPV4_SUFFIX_THREE_HEXTET_BEFORE_ZERO_COMP,
IPV6_IPV4_SUFFIX_FOUR_HEXTET_BEFORE_ZERO_COMP,
IPV6_IPV4_SUFFIX_FIVE_HEXTET_BEFORE_ZERO_COMP,
IPV6_FULL_SPECIFIED,
IPV6_SEVEN_HEXTET_BEFORE_ZERO_COMP,
IPV6_SIX_HEXTET_BEFORE_ZERO_COMP,
IPV6_FIVE_HEXTET_BEFORE_ZERO_COMP,
IPV6_FOUR_HEXTET_BEFORE_ZERO_COMP,
IPV6_THREE_HEXTET_BEFORE_ZERO_COMP,
IPV6_TWO_HEXTET_BEFORE_ZERO_COMP,
IPV6_ONE_HEXTET_BEFORE_ZERO_COMP,
// Must go last because it's the most general and can match ::
IPV6_ZERO_HEXTET_BEFORE_ZERO_COMP,

// I suspect maybe that this one is redundant
//IPV6_ALL_ZEROS,
)

val IPV6_ADDR_AND_PREFIX_LENGTH = SequenceCombinator(IPV6_ADDR, CIDR_SEPARATOR, IntegerTerminal(64, 129))
val IPV6_ADDR_AND_OPTIONAL_PREFIX_LENGTH = SequenceCombinator(IPV6_ADDR, ZeroOrOne(SequenceCombinator(CIDR_SEPARATOR, IntegerTerminal(64, 129))))


var IP_ADDR_AND_PREFIX_LENGTH = AlternativeCombinator(
IPV4_ADDR_AND_OPTIONAL_PREFIX_LENGTH,
IPV6_ADDR_AND_PREFIX_LENGTH)

var IN_ADDR_PREFIX_SPECIAL_VALUES = LiteralChoiceTerminal("any", "localhost", "link-local", "multicast")

var IPV4_ADDR_AND_PREFIX_OR_SPECIAL = AlternativeCombinator(
IPV4_ADDR_AND_OPTIONAL_PREFIX_LENGTH,
IN_ADDR_PREFIX_SPECIAL_VALUES,
)

var IPV6_ADDR_AND_PREFIX_OR_SPECIAL = AlternativeCombinator(
IPV6_ADDR_AND_OPTIONAL_PREFIX_LENGTH,
IN_ADDR_PREFIX_SPECIAL_VALUES,
)


var IP_ADDR_AND_PREFIX_OR_SPECIAL = AlternativeCombinator(
IPV4_ADDR_AND_OPTIONAL_PREFIX_LENGTH,
IPV6_ADDR_AND_OPTIONAL_PREFIX_LENGTH,
IN_ADDR_PREFIX_SPECIAL_VALUES,
)

var IP_ADDR_PREFIX_LIST = SequenceCombinator(IP_ADDR_AND_PREFIX_OR_SPECIAL, ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), IP_ADDR_AND_PREFIX_OR_SPECIAL)))
var IPV4_ADDR_PREFIX_LIST = SequenceCombinator(IPV4_ADDR_AND_PREFIX_OR_SPECIAL, ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), IPV4_ADDR_AND_PREFIX_OR_SPECIAL)))
var IPV6_ADDR_PREFIX_LIST = SequenceCombinator(IPV6_ADDR_AND_PREFIX_OR_SPECIAL, ZeroOrMore(SequenceCombinator(WhitespaceTerminal(), IPV6_ADDR_AND_PREFIX_OR_SPECIAL)))


Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ class EOF : Combinator {
NoMatch
}
}

override fun toStringIndented(indent: Int): String {
return "EOF"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,15 @@ class FlexibleLiteralChoiceTerminal(vararg val choices: String) : TerminalCombin
return NoMatch.copy(longestMatch = offset)
}

override fun toString(): String {
return if (choices.size == 1) {
"Literal(\"${choices[0]}\")"
} else {
"FlexLitChoice(" + choices.joinToString(",") { "\"$it\"" } + ")"
}
}

override fun toStringIndented(indent: Int): String {
return toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ class IntegerTerminal(private val minInclusive: Int,private val maxExclusive: In

return MatchResult(listOf(matchResult.value), offset + matchResult.value.length, listOf(this), offset + matchResult.value.length)
}

override fun toString(): String {
return "Int($minInclusive,$maxExclusive)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,12 @@ class LiteralChoiceTerminal(vararg var choices: String) : TerminalCombinator {
override fun SemanticMatch(value: String, offset: Int): MatchResult {
return match(value, offset)
}

override fun toString(): String {
return if (choices.size == 1) {
"Literal(\"${choices[0]}\")"
} else {
"LitChoice(" + choices.joinToString(",") { "\"$it\"" } + ")"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,19 @@ class OneOrMore(val combinator : Combinator) : Combinator {
override fun SemanticMatch(value: String, offset: Int): MatchResult {
return match(value, offset, combinator::SemanticMatch)
}

override fun toString(): String = toStringIndented(0)

override fun toStringIndented(indent: Int): String {
val prefix = " ".repeat(indent)
val sb = StringBuilder()
sb.append(prefix).append("OneOrMore(\n")
if (combinator is SequenceCombinator || combinator is AlternativeCombinator || combinator is Repeat || combinator is ZeroOrOne || combinator is ZeroOrMore || combinator is OneOrMore) {
sb.append(combinator.toStringIndented(indent + 1)).append("\n")
} else {
sb.append(" ".repeat(indent + 1)).append(combinator.toString()).append("\n")
}
sb.append(prefix).append(")")
return sb.toString()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,9 @@ class OptionalWhitespacePrefix(val combinator: Combinator):
SequenceCombinator(WhitespaceTerminal(), combinator),
combinator
) {
//
// override fun SyntacticMatch(value: String, offset: Int): MatchResult {
// var newOffset = offset
// for(o in offset..<value.length) {
// if (value[o].isWhitespace()) {
// newOffset = o + 1
// } else {
// break
// }
// }
//
// return combinator.SyntacticMatch(value, newOffset)
// }
//
// override fun SemanticMatch(value: String, offset: Int): MatchResult {
// var newOffset = offset
// for(o in offset..<value.length) {
// if (value[o].isWhitespace()) {
// newOffset = o + 1
// } else {
// break
// }
// }
//
// return combinator.SemanticMatch(value, newOffset)
// }


override fun toString(): String {
return "\\s*{${combinator}}"
}
}
Loading
Loading