Add sanatizer for send_message.

This commit is contained in:
connor rigby 2017-11-14 14:15:46 -08:00
parent c23a6bbc04
commit c08e37e148
6 changed files with 129 additions and 34 deletions

View file

@ -36,7 +36,7 @@ defmodule Farmbot.BotState.Transport.GenMQTT do
def handle_log_events(logs, {%{client: client} = internal_state, old_bot_state}) do
for %Farmbot.Log{} = log <- logs do
if log.module == nil or Module.split(log.module || Elixir.Logger) |> List.first == "Farmbot" do
if log.module != nil or Module.split(log.module || Elixir.Logger) |> List.first == "Farmbot" and (log.verbosity || 0) < 3 do
location_data = Map.get(old_bot_state || %{}, :location_data, %{position: %{x: -1, y: -1, z: -1}})
meta = %{type: log.level, x: nil, y: nil, z: nil}
log_without_pos = %{created_at: log.time, meta: meta, channels: log.meta[:channels] || [], message: log.message}

View file

@ -7,41 +7,90 @@ defmodule Farmbot.CeleryScript.AST.Node.SendMessage do
def execute(%{message: m, message_type: type}, channels, env) do
env = mutate_env(env)
{:ok, env, channels} = do_reduce(channels, env, [])
msg = String.replace(m, "{{", "<%=")
|> String.replace("}}", "%>")
|> EEx.eval_string(fetch_bindings())
case type do
"debug" ->
Logger.debug 2, msg, channels: channels
{:ok, env}
"info" ->
Logger.info 2, msg, channels: channels
{:ok, env}
"busy" ->
Logger.busy 2, msg, channels: channels
{:ok, env}
"success" ->
Logger.success 2, msg, channels: channels
{:ok, env}
"warn" ->
Logger.warn 2, msg, channels: channels
{:ok, env}
"error" ->
Logger.error 2, msg, channels: channels
{:ok, env}
other ->
{:error, "unknown type: #{other}", env}
bindings = fetch_bindings()
case sanatize(m) do
{:ok, sanatized} ->
msg = EEx.eval_string(sanatized, bindings)
case type do
"debug" ->
Logger.debug 2, msg, channels: channels
{:ok, env}
"info" ->
Logger.info 2, msg, channels: channels
{:ok, env}
"busy" ->
Logger.busy 2, msg, channels: channels
{:ok, env}
"success" ->
Logger.success 2, msg, channels: channels
{:ok, env}
"warn" ->
Logger.warn 2, msg, channels: channels
{:ok, env}
"error" ->
Logger.error 2, msg, channels: channels
{:ok, env}
other ->
{:error, "unknown type: #{other}", env}
end
{:error, reason} -> {:error, reason, env}
end
rescue
e in CompileError ->
{:error, Exception.message(e), env}
case Exception.message(e) do
"nofile:1: undefined function " <> undef ->
Logger.error 2, "Unknown variable: #{undef}"
{:error, :unknown_variable, env}
_ ->
{:error, Exception.message(e), env}
end
e -> reraise(e, System.stacktrace())
end
defp sanatize(message, global_acc \\ "", local_acc \\ "", mode \\ :global)
defp sanatize(<<>>, global, _local, _mode), do: {:ok, global}
defp sanatize(<<"{{", rest :: binary >>, acc, _local, _mode) do
sanatize(rest, acc <> "<%=", "", :local)
end
defp sanatize(<<"}}", rest ::binary >>, global_acc, local_acc, _) do
case sanatize_local(local_acc) do
{:ok, sanatized} ->
sanatize(rest, global_acc <> sanatized <> "%>", "", :global)
{:error, reason} -> {:error, reason}
end
end
defp sanatize(<<char, rest :: binary>>, global, _local, :global) do
sanatize(rest, global <> <<char>>, "", :global)
end
defp sanatize(<<char, rest :: binary>>, global, local, :local) do
sanatize(rest, global, local <> <<char>>, :local)
end
defp sanatize_local(local) do
cond do
String.contains?(local, "(") or String.contains?(local, ")") ->
{:error, "templates may not contain special characters: `(` or `)`"}
String.contains?(local, ".") ->
{:error, "templates may not contain special character: `.`"}
String.contains?(local, ":") ->
{:error, "templates may not contain special character: `.`"}
String.contains?(local, "[") or String.contains?(local, "]") ->
{:error, "templates may not contain special characters: `[` or `]`"}
String.contains?(local, "\"") or String.contains?(local, "\'") or String.contains?(local, "\`") ->
{:error, "templates may not sub strings."}
true -> {:ok, local}
end
end
defp fetch_bindings do
bot_state = Farmbot.BotState.force_state_push()
pins = Enum.map(bot_state.pins, fn({pin, %{value: value}}) -> {pin, value} end)
pins = Enum.map(bot_state.pins, fn({pin, %{value: value}}) -> {:"pin_#{pin}", value} end)
location = Enum.map(bot_state.location_data.position, fn({axis, val}) -> {axis, val} end)
pins ++ location
end

View file

@ -19,11 +19,19 @@ defmodule Farmbot.FarmEvent.Manager do
@checkup_time 20_000
def wait_for_sync do
GenServer.call(__MODULE__, :wait_for_sync)
end
def resume do
GenServer.call(__MODULE__, :resume)
end
## GenServer
defmodule State do
@moduledoc false
defstruct [timer: nil, last_time_index: %{}]
defstruct [timer: nil, last_time_index: %{}, wait_for_sync: true]
end
@doc false
@ -32,11 +40,29 @@ defmodule Farmbot.FarmEvent.Manager do
end
def init([]) do
send self(), :checkup
{:ok, struct(State)}
end
def handle_info(:checkup, state) do
def handle_call(:wait_for_sync, _, state) do
if state.timer do
Process.cancel_timer(state.timer)
end
Logger.warn 3, "Pausing FarmEvent Execution until sync."
{:reply, :ok, %{state | wait_for_sync: true}}
end
def handle_call(:resume, _, state) do
send self(), :checkup
Logger.success 3, "Resuming FarmEvents."
{:reply, :ok, %{state | wait_for_sync: false}}
end
def handle_info(:checkup, %{wait_for_sync: true} = state) do
Logger.warn 3, "Waiting for sync before running FarmEvents."
{:noreply, state}
end
def handle_info(:checkup, %{wait_for_sync: false} = state) do
now = get_now()
all_events = Farmbot.Repo.current_repo().all(Farmbot.Repo.FarmEvent)
@ -156,6 +182,15 @@ defmodule Farmbot.FarmEvent.Manager do
# Checks if we shoudl run a sequence or not. returns {event | nil, time | nil}
defp should_run_sequence?(calendar, last_time, now)
defp should_run_sequence?(nil, last_time, now) do
Logger.debug 3, "Checking sequence with no calendar."
if is_nil(last_time) do
{true, now}
else
{false, last_time}
end
end
# if there is no last time, check if time is passed now within 60 seconds.
defp should_run_sequence?([first_time | _], nil, now) do;

View file

@ -8,7 +8,7 @@ defmodule Farmbot.FarmEvent.Supervisor do
def init([]) do
children = [
# worker(Farmbot.FarmEvent.Manager, [])
worker(Farmbot.FarmEvent.Manager, [])
]
supervise(children, strategy: :one_for_one)

View file

@ -59,11 +59,17 @@ defmodule Farmbot.Repo do
def init([repo_a, repo_b]) do
# Delete any old sync cmds.
destroy_all_sync_cmds()
if Process.whereis(Farmbot.FarmEvent.Manager) do
Farmbot.FarmEvent.Manager.wait_for_sync()
end
needs_hard_sync = if ConfigStorage.get_config_value(:bool, "settings", "first_sync") || auto_sync?() do
do_sync_both(repo_a, repo_b)
ConfigStorage.update_config_value(:bool, "settings", "first_sync", false)
BotState.set_sync_status(:synced)
if Process.whereis(Farmbot.FarmEvent.Manager) do
Farmbot.FarmEvent.Manager.resume()
end
false
else
BotState.set_sync_status(:sync_now)
@ -90,6 +96,7 @@ defmodule Farmbot.Repo do
def handle_call(:force_hard_sync, _, state) do
maybe_cancel_timer(state.timer)
BotState.set_sync_status(:sync_now)
Farmbot.FarmEvent.Manager.wait_for_sync()
{:reply, :ok, %{state | timer: nil, needs_hard_sync: true}}
end
@ -103,6 +110,7 @@ defmodule Farmbot.Repo do
def handle_call(:flip, _, %{repos: [repo_a, repo_b], needs_hard_sync: true} = state) do
maybe_cancel_timer(state.timer)
Farmbot.FarmEvent.Manager.wait_for_sync()
destroy_all_sync_cmds()
Logger.warn 3, "Forcing full sync."
BotState.set_sync_status(:syncing)
@ -110,12 +118,14 @@ defmodule Farmbot.Repo do
BotState.set_sync_status(:synced)
copy_configs(repo_b)
flip_repos_in_cs()
Farmbot.FarmEvent.Manager.resume()
{:reply, :ok, %{state | repos: [repo_b, repo_a], needs_hard_sync: false, timer: start_timer()}}
end
def handle_call(:flip, _, %{repos: [repo_a, repo_b]} = state) do
maybe_cancel_timer(state.timer)
Logger.busy 3, "Syncing"
Farmbot.FarmEvent.Manager.wait_for_sync()
BotState.set_sync_status(:syncing)
# Fetch all sync_cmds and apply them in order they were received.
@ -128,6 +138,7 @@ defmodule Farmbot.Repo do
copy_configs(repo_b)
destroy_all_sync_cmds()
Logger.success 3, "Sync complete."
Farmbot.FarmEvent.Manager.resume()
{:reply, repo_b, %{state | repos: [repo_b, repo_a], timer: start_timer()}}
end
@ -247,7 +258,7 @@ defmodule Farmbot.Repo do
case repo.get(mod, id) do
# If it does not, just return the newly created object.
nil ->
mod.changeset(obj, %{})
mod.changeset(struct(mod), not_struct)
|> repo.insert!
# if there is an existing record, copy the ecto meta from the old

View file

@ -83,7 +83,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
email = ConfigStorage.get_config_value(:string, "authorization", "email")
pass = ConfigStorage.get_config_value(:string, "authorization", "password")
server = ConfigStorage.get_config_value(:string, "authorization", "server")
network = !(Enum.empty?(ConfigStorage.all(ConfigStorage.NetworkInterface.NetworkInterface)))
network = !(Enum.empty?(ConfigStorage.all(ConfigStorage.NetworkInterface)))
if email && pass && server && network do
conn = render_page(conn, "finish")
spawn fn() ->