blob: 57f5445d253d0a90041e8e347bd9adb6331c8bf4 [file] [log] [blame] [raw]
package li.cil.oc.server.component
import java.io._
import java.net.{HttpURLConnection, URL}
import java.nio.charset.MalformedInputException
import java.util.concurrent.Future
import java.util.regex.Matcher
import li.cil.oc.api.network._
import li.cil.oc.util.{ThreadPoolFactory, WirelessNetwork}
import li.cil.oc.{Settings, api}
import net.minecraft.nbt.NBTTagCompound
import net.minecraft.server.MinecraftServer
import net.minecraft.tileentity.TileEntity
import scala.collection.convert.WrapAsScala._
import scala.language.implicitConversions
class WirelessNetworkCard(val owner: TileEntity) extends NetworkCard {
override val node = api.Network.newNode(this, Visibility.Network).
withComponent("modem", Visibility.Neighbors).
withConnector().
create()
var strength = 0.0
var response: Option[Future[_]] = None
// ----------------------------------------------------------------------- //
@LuaCallback(value = "getStrength", direct = true)
def getStrength(context: Context, args: Arguments): Array[AnyRef] = result(strength)
@LuaCallback("setStrength")
def setStrength(context: Context, args: Arguments): Array[AnyRef] = {
strength = math.max(args.checkDouble(0), math.min(0, Settings.get.maxWirelessRange))
result(strength)
}
override def isWireless(context: Context, args: Arguments): Array[AnyRef] = result(true)
@LuaCallback(value = "isHttpEnabled", direct = true)
def isHttpEnabled(context: Context, args: Arguments): Array[AnyRef] = result(Settings.get.httpEnabled)
override def send(context: Context, args: Arguments) = {
val address = args.checkString(0)
if (isHttpRequest(address)) {
checkAddress(address)
val post = if (args.isString(1)) Option(args.checkString(1)) else None
WirelessNetworkCard.threadPool.submit(new Runnable {
def run() = try {
val proxy = Option(MinecraftServer.getServer.getServerProxy).getOrElse(java.net.Proxy.NO_PROXY)
val url = new URL(address)
url.openConnection(proxy) match {
case http: HttpURLConnection => try {
http.setDoInput(true)
if (post.isDefined) {
http.setRequestMethod("POST")
http.setDoOutput(true)
val out = new BufferedWriter(new OutputStreamWriter(http.getOutputStream))
out.write(post.get)
out.close()
}
else {
http.setRequestMethod("GET")
http.setDoOutput(false)
}
var part: Option[String] = None
for (line <- scala.io.Source.fromInputStream(http.getInputStream).getLines()) {
if ((part + line).length <= Settings.get.maxNetworkPacketSize) {
part = Some(part.getOrElse("") + line + "\n")
}
else {
context.signal("http_response", address, part.get)
part = None
}
}
context.signal("http_response", address, part.orNull)
if (part.isDefined) {
context.signal("http_response", address)
}
}
finally {
http.disconnect()
}
case other => context.signal("http_response", address, Unit, "connection failed")
}
}
catch {
case e: FileNotFoundException =>
context.signal("http_response", address, Unit, "not found: " + Option(e.getMessage).getOrElse(e.toString))
case _: MalformedInputException =>
context.signal("http_response", address, Unit, "binary data not yet supported")
case e: Throwable =>
context.signal("http_response", address, Unit, Option(e.getMessage).getOrElse(e.toString))
}
})
result(true)
}
else {
val port = checkPort(args.checkInteger(1))
checkPacketSize(args.drop(2))
if (strength > 0) {
checkPower()
for ((card, distance) <- WirelessNetwork.computeReachableFrom(this)
if card.node.address == address && card.openPorts.contains(port)) {
card.node.sendToReachable("computer.signal",
Seq("modem_message", node.address, Int.box(port), Double.box(distance)) ++ args.drop(2): _*)
}
}
super.send(context, args)
}
}
override def broadcast(context: Context, args: Arguments) = {
val port = checkPort(args.checkInteger(0))
checkPacketSize(args.drop(1))
if (strength > 0) {
checkPower()
for ((card, distance) <- WirelessNetwork.computeReachableFrom(this)
if card.openPorts.contains(port)) {
card.node.sendToReachable("computer.signal",
Seq("modem_message", node.address, Int.box(port), Double.box(distance)) ++ args.drop(1): _*)
}
}
super.broadcast(context, args)
}
private def checkPower() {
val cost = Settings.get.wirelessCostPerRange
if (cost > 0 && !Settings.get.ignorePower) {
if (!node.tryChangeBuffer(-strength * cost)) {
throw new IOException("not enough energy")
}
}
}
private def isHttpRequest(address: String) = {
try {
new URL(address)
true
}
catch {
case e: Throwable => false
}
}
private def checkAddress(address: String) {
val url = new URL(address)
val protocol = url.getProtocol
if (!protocol.matches("^https?$")) {
throw new FileNotFoundException("unsupported protocol")
}
val host = Matcher.quoteReplacement(url.getHost)
if (Settings.get.httpHostWhitelist.length > 0 && !Settings.get.httpHostWhitelist.exists(host.matches)) {
throw new FileNotFoundException("domain is not whitelisted")
}
if (Settings.get.httpHostBlacklist.exists(host.matches)) {
throw new FileNotFoundException("domain is blacklisted")
}
}
// ----------------------------------------------------------------------- //
override val canUpdate = true
override def update() {
super.update()
WirelessNetwork.update(this)
}
override def onConnect(node: Node) {
super.onConnect(node)
if (node == this.node) {
WirelessNetwork.add(this)
}
}
override def onDisconnect(node: Node) {
super.onDisconnect(node)
if (node == this.node) {
val removed = WirelessNetwork.remove(this)
assert(removed)
}
}
// ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) {
super.load(nbt)
strength = nbt.getDouble("strength") max 0 min Settings.get.maxWirelessRange
}
override def save(nbt: NBTTagCompound) {
super.save(nbt)
nbt.setDouble("strength", strength)
}
}
object WirelessNetworkCard {
private val threadPool = ThreadPoolFactory.create("HTTP", Settings.get.httpThreads)
}