ShareTargetParser.kt
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.concept.engine.manifest.parser
import mozilla.components.concept.engine.manifest.WebAppManifest.ShareTarget
import mozilla.components.support.ktx.android.org.json.asSequence
import mozilla.components.support.ktx.android.org.json.toJSONArray
import mozilla.components.support.ktx.android.org.json.tryGetString
import org.json.JSONArray
import org.json.JSONObject
import java.util.Locale
internal object ShareTargetParser {
/**
* Parses a share target inside a web app manifest.
*/
fun parse(json: JSONObject?): ShareTarget? {
val action = json?.tryGetString("action") ?: return null
val method = parseMethod(json.tryGetString("method"))
val encType = parseEncType(json.tryGetString("enctype"))
val params = json.optJSONObject("params")
return if (method != null && encType != null && validMethodAndEncType(method, encType)) {
return ShareTarget(
action = action,
method = method,
encType = encType,
params = ShareTarget.Params(
title = params?.tryGetString("title"),
text = params?.tryGetString("text"),
url = params?.tryGetString("url"),
files = parseFiles(params),
),
)
} else {
null
}
}
/**
* Serializes a share target to JSON for a web app manifest.
*/
fun serialize(shareTarget: ShareTarget?): JSONObject? {
shareTarget ?: return null
return JSONObject().apply {
put("action", shareTarget.action)
put("method", shareTarget.method.name)
put("enctype", shareTarget.encType.type)
val params = JSONObject().apply {
put("title", shareTarget.params.title)
put("text", shareTarget.params.text)
put("url", shareTarget.params.url)
put(
"files",
shareTarget.params.files.asSequence()
.map { file ->
JSONObject().apply {
put("name", file.name)
putOpt("accept", file.accept.toJSONArray())
}
}
.asIterable()
.toJSONArray(),
)
}
put("params", params)
}
}
/**
* Convert string to [ShareTarget.RequestMethod]. Returns null if the string is invalid.
*/
private fun parseMethod(method: String?): ShareTarget.RequestMethod? {
method ?: return ShareTarget.RequestMethod.GET
return try {
ShareTarget.RequestMethod.valueOf(method.uppercase(Locale.ROOT))
} catch (e: IllegalArgumentException) {
null
}
}
/**
* Convert string to [ShareTarget.EncodingType]. Returns null if the string is invalid.
*/
private fun parseEncType(encType: String?): ShareTarget.EncodingType? {
val typeString = encType?.lowercase(Locale.ROOT) ?: return ShareTarget.EncodingType.URL_ENCODED
return ShareTarget.EncodingType.entries.find { it.type == typeString }
}
/**
* Checks that [encType] is URL_ENCODED (if [method] is GET or POST) or MULTIPART (only if POST)
*/
private fun validMethodAndEncType(
method: ShareTarget.RequestMethod,
encType: ShareTarget.EncodingType,
) = when (encType) {
ShareTarget.EncodingType.URL_ENCODED -> true
ShareTarget.EncodingType.MULTIPART -> method == ShareTarget.RequestMethod.POST
}
private fun parseFiles(params: JSONObject?) =
when (val files = params?.opt("files")) {
is JSONObject -> listOfNotNull(parseFile(files))
is JSONArray -> files.asSequence { i -> getJSONObject(i) }
.mapNotNull(::parseFile)
.toList()
else -> emptyList()
}
private fun parseFile(file: JSONObject): ShareTarget.Files? {
val name = file.tryGetString("name")
val accept = file.opt("accept")
if (name.isNullOrEmpty()) return null
return ShareTarget.Files(
name = name,
accept = when (accept) {
is String -> listOf(accept)
is JSONArray -> accept.asSequence { i -> getString(i) }.toList()
else -> emptyList()
},
)
}
}