Skip to content
Permalink
Browse files
fix(lists): Escape special characters in comment, label, and name of …
…a list node (DSP-1529) (#1846)
  • Loading branch information
SepidehAlassi committed Apr 26, 2021
1 parent f69f008 commit f96c069a360f277c580dfd17bd0c0db0f587b018
@@ -39,7 +39,12 @@ import org.knora.webapi._
import org.knora.webapi.exceptions._
import org.knora.webapi.messages.IriConversions._
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM
import org.knora.webapi.messages.store.triplestoremessages.{SparqlAskRequest, SparqlAskResponse}
import org.knora.webapi.messages.store.triplestoremessages.{
SparqlAskRequest,
SparqlAskResponse,
StringLiteralSequenceV2,
StringLiteralV2
}
import org.knora.webapi.messages.v1.responder.projectmessages.ProjectInfoV1
import org.knora.webapi.messages.v2.responder.KnoraContentV2
import org.knora.webapi.messages.v2.responder.standoffmessages._
@@ -3248,4 +3253,12 @@ class StringFormatter private (val maybeSettings: Option[KnoraSettingsImpl] = No
throw BadRequestException(s"Internal ontology <$iri> cannot be served")
}
}

def unescapeStringLiteralSeq(stringLiteralSeq: StringLiteralSequenceV2): StringLiteralSequenceV2 = {
StringLiteralSequenceV2(
stringLiterals = stringLiteralSeq.stringLiterals.map(stringLiteral =>
StringLiteralV2(value = fromSparqlEncodedString(stringLiteral.value), language = stringLiteral.language))
)

}
}
@@ -125,6 +125,30 @@ case class CreateNodeApiRequestADM(id: Option[IRI] = None,
}

def toJsValue: JsValue = createListNodeApiRequestADMFormat.write(this)

/**
* Escapes special characters within strings
*
*/
def escape: CreateNodeApiRequestADM = {
val escapedLabels: Seq[StringLiteralV2] = labels.map { label =>
val escapedLabel =
stringFormatter.toSparqlEncodedString(label.value, throw BadRequestException(s"Invalid label: ${label.value}"))
StringLiteralV2(value = escapedLabel, language = label.language)
}
val escapedComments = comments.map { comment =>
val escapedComment =
stringFormatter.toSparqlEncodedString(comment.value,
throw BadRequestException(s"Invalid comment: ${comment.value}"))
StringLiteralV2(value = escapedComment, language = comment.language)
}
val escapedName: Option[String] = name match {
case None => None
case Some(value: String) =>
Some(stringFormatter.toSparqlEncodedString(value, throw BadRequestException(s"Invalid string: $value")))
}
copy(labels = escapedLabels, comments = escapedComments, name = escapedName)
}
}

/**
@@ -634,6 +658,23 @@ case class ListRootNodeInfoADM(id: IRI,
)
}

/**
* unescapes the special characters in labels, comments, and name for comparison in tests.
*
*/
def unescape: ListRootNodeInfoADM = {
val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance

val unescapedLabels = stringFormatter.unescapeStringLiteralSeq(labels)

val unescapedComments = stringFormatter.unescapeStringLiteralSeq(comments)
val unescapedName: Option[String] = name match {
case None => None
case Some(value) => Some(stringFormatter.fromSparqlEncodedString(value))
}
copy(name = unescapedName, labels = unescapedLabels, comments = unescapedComments)
}

/**
* Gets the label in the user's preferred language.
*
@@ -682,6 +723,23 @@ case class ListChildNodeInfoADM(id: IRI,
)
}

/**
* unescapes the special characters in labels, comments, and name for comparison in tests.
*
*/
def unescape: ListChildNodeInfoADM = {
val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance

val unescapedLabels = stringFormatter.unescapeStringLiteralSeq(labels)

val unescapedComments = stringFormatter.unescapeStringLiteralSeq(comments)
val unescapedName: Option[String] = name match {
case None => None
case Some(value) => Some(stringFormatter.fromSparqlEncodedString(value))
}
copy(name = unescapedName, labels = unescapedLabels, comments = unescapedComments)
}

/**
* Gets the label in the user's preferred language.
*
@@ -790,6 +848,22 @@ case class ListRootNodeADM(id: IRI,
)
}

/**
* unescapes the special characters in labels, comments, and name for comparison in tests.
*
*/
def unescape: ListRootNodeADM = {
val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance

val unescapedLabels = stringFormatter.unescapeStringLiteralSeq(labels)
val unescapedComments = stringFormatter.unescapeStringLiteralSeq(comments)
val unescapedName: Option[String] = name match {
case None => None
case Some(value) => Some(stringFormatter.fromSparqlEncodedString(value))
}
copy(name = unescapedName, labels = unescapedLabels, comments = unescapedComments)
}

/**
* Gets the label in the user's preferred language.
*
@@ -850,6 +924,23 @@ case class ListChildNodeADM(id: IRI,
)
}

/**
* unescapes the special characters in labels, comments, and name for comparison in tests.
*
*/
def unescape: ListChildNodeADM = {
val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance

val unescapedLabels = stringFormatter.unescapeStringLiteralSeq(labels)
val unescapedComments = stringFormatter.unescapeStringLiteralSeq(comments)

val unescapedName: Option[String] = name match {
case None => None
case Some(value) => Some(stringFormatter.fromSparqlEncodedString(value))
}
copy(name = unescapedName, labels = unescapedLabels, comments = unescapedComments)
}

/**
* Gets the label in the user's preferred language.
*
@@ -154,7 +154,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
name = name,
labels = StringLiteralSequenceV2(labels.toVector.sortBy(_.language)),
comments = StringLiteralSequenceV2(comments.toVector.sortBy(_.language))
)
).unescape
}

// _ = log.debug("listsGetAdminRequest - items: {}", items)
@@ -200,7 +200,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde

rootNodeInfo = maybeRootNodeInfo match {
case Some(info: ListRootNodeInfoADM) => info.asInstanceOf[ListRootNodeInfoADM]
case Some(info: ListChildNodeInfoADM) =>
case Some(_: ListChildNodeInfoADM) =>
throw InconsistentRepositoryDataException(
"A child node info was found, although we are expecting a root node info. Please report this as a possible bug.")
case Some(_) | None =>
@@ -393,7 +393,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
.map(_.head.asInstanceOf[StringLiteralV2].value),
labels = StringLiteralSequenceV2(labels.toVector.sortBy(_.language)),
comments = StringLiteralSequenceV2(comments.toVector.sortBy(_.language))
)
).unescape
} else {
ListChildNodeInfoADM(
id = nodeIri.toString,
@@ -408,7 +408,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
hasRootNode = hasRootNodeOption.getOrElse(
throw InconsistentRepositoryDataException(
s"Required hasRootNode property missing for list node $nodeIri."))
)
).unescape
}
}
Some(nodeInfo)
@@ -663,7 +663,7 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
children = children.map(_.sorted),
position = position,
hasRootNode = hasRootNode
)
).unescape
}

for {
@@ -891,8 +891,10 @@ class ListsResponderADM(responderData: ResponderData) extends Responder(responde
/* verify that the list node name is unique for the project */
projectUniqueNodeName <- listNodeNameIsProjectUnique(createNodeRequest.projectIri, createNodeRequest.name)
_ = if (!projectUniqueNodeName) {
val escapedName = createNodeRequest.name.get
val unescapedName = stringFormatter.fromSparqlEncodedString(escapedName)
throw BadRequestException(
s"The node name ${createNodeRequest.name.get} is already used by a list inside the project ${createNodeRequest.projectIri}.")
s"The node name ${unescapedName} is already used by a list inside the project ${createNodeRequest.projectIri}.")
}

// calculate the data named graph
@@ -142,15 +142,15 @@ class NewListsRouteADMFeature(routeData: KnoraRouteData)
createRequest = if (apiRequest.parentNodeIri.isEmpty) {
// No, create a new list with given information of its root node.
ListCreateRequestADM(
createRootNode = apiRequest,
createRootNode = apiRequest.escape,
featureFactoryConfig = featureFactoryConfig,
requestingUser = requestingUser,
apiRequestID = UUID.randomUUID()
)
} else {
// Yes, create a new child and attach it to the parent node.
ListChildNodeCreateRequestADM(
createChildNodeRequest = apiRequest,
createChildNodeRequest = apiRequest.escape,
featureFactoryConfig = featureFactoryConfig,
requestingUser = requestingUser,
apiRequestID = UUID.randomUUID()
@@ -128,7 +128,7 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData)
)
} yield
ListCreateRequestADM(
createRootNode = apiRequest,
createRootNode = apiRequest.escape,
featureFactoryConfig = featureFactoryConfig,
requestingUser = requestingUser,
apiRequestID = UUID.randomUUID()
@@ -266,7 +266,7 @@ class OldListsRouteADMFeature(routeData: KnoraRouteData)
)
} yield
ListChildNodeCreateRequestADM(
createChildNodeRequest = apiRequest,
createChildNodeRequest = apiRequest.escape,
featureFactoryConfig = featureFactoryConfig,
requestingUser = requestingUser,
apiRequestID = UUID.randomUUID()
@@ -26,6 +26,7 @@ import akka.testkit._
import com.typesafe.config.{Config, ConfigFactory}
import org.knora.webapi._
import org.knora.webapi.exceptions.{BadRequestException, DuplicateValueException, UpdateNotPerformedException}
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages.admin.responder.listsmessages._
import org.knora.webapi.messages.store.triplestoremessages.{RdfDataObject, StringLiteralV2}
import org.knora.webapi.sharedtestdata.SharedTestDataV1._
@@ -51,6 +52,7 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with

// The default timeout for receiving reply messages from actors.
implicit private val timeout = 5.seconds
private implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance

override lazy val rdfDataObjects = List(
RdfDataObject(path = "test_data/demo_data/images-demo-data.ttl", name = "http://www.knora.org/data/00FF/images"),
@@ -179,7 +181,7 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with
name = Some("neuelistename"),
labels = Seq(StringLiteralV2(value = "Neue Liste", language = Some("de"))),
comments = Seq.empty[StringLiteralV2]
),
).escape,
featureFactoryConfig = defaultFeatureFactoryConfig,
requestingUser = SharedTestDataADM.imagesUser01,
apiRequestID = UUID.randomUUID
@@ -206,8 +208,46 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with
newListIri.set(listInfo.id)
}

"create a list with special characters in its labels" in {
val labelWithSpecialCharacter = "Neue \\\"Liste\\\""
val commentWithSpecialCharacter = "Neue \\\"Kommentar\\\""
val nameWithSpecialCharacter = "a new \\\"name\\\""
responderManager ! ListCreateRequestADM(
createRootNode = CreateNodeApiRequestADM(
projectIri = IMAGES_PROJECT_IRI,
name = Some(nameWithSpecialCharacter),
labels = Seq(StringLiteralV2(value = labelWithSpecialCharacter, language = Some("de"))),
comments = Seq(StringLiteralV2(value = commentWithSpecialCharacter, language = Some("de"))),
).escape,
featureFactoryConfig = defaultFeatureFactoryConfig,
requestingUser = SharedTestDataADM.imagesUser01,
apiRequestID = UUID.randomUUID
)

val received: ListGetResponseADM = expectMsgType[ListGetResponseADM](timeout)

val listInfo = received.list.listinfo
listInfo.projectIri should be(IMAGES_PROJECT_IRI)

listInfo.name should be(Some(stringFormatter.fromSparqlEncodedString(nameWithSpecialCharacter)))

val labels: Seq[StringLiteralV2] = listInfo.labels.stringLiterals
labels.size should be(1)
val givenLabel = labels.head
givenLabel.value shouldEqual stringFormatter.fromSparqlEncodedString(labelWithSpecialCharacter)
givenLabel.language shouldEqual Some("de")

val comments = received.list.listinfo.comments.stringLiterals
val givenComment = comments.head
givenComment.language shouldEqual Some("de")
givenComment.value shouldEqual stringFormatter.fromSparqlEncodedString(commentWithSpecialCharacter)

val children = received.list.children
children.size should be(0)
}

"update basic list information" in {
responderManager ! NodeInfoChangeRequestADM(
val changeNodeInfoRequest = NodeInfoChangeRequestADM(
listIri = newListIri.get,
changeNodeRequest = ChangeNodeInfoApiRequestADM(
listIri = newListIri.get,
@@ -216,18 +256,19 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with
labels = Some(
Seq(
StringLiteralV2(value = "Neue geänderte Liste", language = Some("de")),
StringLiteralV2(value = "Changed list", language = Some("en"))
StringLiteralV2(value = "Changed List", language = Some("en"))
)),
comments = Some(
Seq(
StringLiteralV2(value = "Neuer Kommentar", language = Some("de")),
StringLiteralV2(value = "New comment", language = Some("en"))
StringLiteralV2(value = "New Comment", language = Some("en"))
))
),
featureFactoryConfig = defaultFeatureFactoryConfig,
requestingUser = SharedTestDataADM.imagesUser01,
apiRequestID = UUID.randomUUID
)
responderManager ! changeNodeInfoRequest

val received: RootNodeInfoGetResponseADM = expectMsgType[RootNodeInfoGetResponseADM](timeout)

@@ -239,15 +280,15 @@ class ListsResponderADMSpec extends CoreSpec(ListsResponderADMSpec.config) with
labels.sorted should be(
Seq(
StringLiteralV2(value = "Neue geänderte Liste", language = Some("de")),
StringLiteralV2(value = "Changed list", language = Some("en"))
StringLiteralV2(value = "Changed List", language = Some("en"))
).sorted)

val comments = listInfo.comments.stringLiterals
comments.size should be(2)
comments.sorted should be(
Seq(
StringLiteralV2(value = "Neuer Kommentar", language = Some("de")),
StringLiteralV2(value = "New comment", language = Some("en"))
StringLiteralV2(value = "New Comment", language = Some("en"))
).sorted)
}

0 comments on commit f96c069

Please sign in to comment.