Skip to content

Commit

Permalink
🌟 Support data URLs in open_url (fonsp#1670)
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsp authored Nov 16, 2021
1 parent 81a3cf5 commit fecd831
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/Pluto.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ include("./packages/PkgUtils.jl")
include("./evaluation/Run.jl")
include("./evaluation/RunBonds.jl")

module DownloadCool include("./webserver/data_url.jl") end
include("./webserver/MsgPack.jl")
include("./webserver/SessionActions.jl")
include("./webserver/Static.jl")
Expand Down
15 changes: 11 additions & 4 deletions src/webserver/Firebasey.jl
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ end
md"## `@skip_as_script`"

# ╔═║ e907d862-2de1-11eb-11a9-4b3ac37cb0f3
function skip_as_script(m::Module)
function is_inside_pluto(m::Module)
if isdefined(m, :PlutoForceDisplay)
return m.PlutoForceDisplay
else
Expand All @@ -624,7 +624,7 @@ end
Marks a expression as Pluto-only, which means that it won't be executed when running outside Pluto. Do not use this for your own projects.
"""
macro skip_as_script(ex)
if skip_as_script(__module__)
if is_inside_pluto(__module__)
esc(ex)
else
nothing
Expand All @@ -633,7 +633,7 @@ end

# ╔═║ c2c2b057-a88f-4cc6-ada4-fc55ac29931e
"The opposite of `@skip_as_script`"
macro only_as_script(ex) skip_as_script(__module__) ? nothing : esc(ex) end
macro only_as_script(ex) is_inside_pluto(__module__) ? nothing : esc(ex) end

# ╔═║ e748600a-2de1-11eb-24be-d5f0ecab8fa4
# Only define this in Pluto - assume we are `using Test` otherwise
Expand All @@ -646,7 +646,14 @@ begin
end
# Do nothing inside pluto (so we don't need to have Test as dependency)
# test/Firebasey is `using Test` before including this file
@only_as_script ((@isdefined Test) ? nothing : macro test(expr) quote nothing end end)
@only_as_script begin
if !isdefined(@__MODULE__, Symbol("@test"))
macro test(e...) nothing; end
macro test_throws(e...) nothing; end
macro test_broken(e...) nothing; end
macro testset(e...) nothing; end
end
end
end

# ╔═║ 5ddfd616-db20-451b-bc1e-2ad52e0e2777
Expand Down
3 changes: 2 additions & 1 deletion src/webserver/SessionActions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module SessionActions

import ..Pluto: ServerSession, Notebook, Cell, emptynotebook, tamepath, new_notebooks_directory, without_pluto_file_extension, numbered_until_new, readwrite, update_save_run!, update_from_file, wait_until_file_unchanged, putnotebookupdates!, putplutoupdates!, load_notebook, clientupdate_notebook_list, WorkspaceManager, @asynclog
using FileWatching
import ..Pluto.DownloadCool: download_cool

struct NotebookIsRunningException <: Exception
notebook::Notebook
Expand All @@ -16,7 +17,7 @@ function Base.showerror(io::IO, e::UserError)
end

function open_url(session::ServerSession, url::AbstractString; kwargs...)
path = download(url, emptynotebook().path)
path = download_cool(url, emptynotebook().path)
open(session, path; kwargs...)
end

Expand Down
165 changes: 165 additions & 0 deletions src/webserver/data_url.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
### A Pluto.jl notebook ###
# v0.17.1

using Markdown
using InteractiveUtils

# ╔═║ cc180e7e-46c3-11ec-3fff-05e1b5c77986
md"""
# Download Data URLs
"""

# ╔═║ 2385dd3b-15f8-4790-907f-e0576a56c4c0
random_data = rand(UInt8, 30)

# ╔═║ d8ed6d44-33cd-4c9d-828b-d237d43769f5
# try
# download("asdffads")
# catch e
# e
# end |> typeof

# ╔═║ e1610184-5d16-499b-883e-7ef92f402ebb
function is_inside_pluto(m::Module)
if isdefined(m, :PlutoForceDisplay)
return m.PlutoForceDisplay
else
isdefined(m, :PlutoRunner) && parentmodule(m) == Main
end
end

# ╔═║ b987a8a2-6ab0-4e88-af3c-d7f2778af657
begin
if is_inside_pluto(@__MODULE__)
import Pkg

# create a local environment for this notebook
# used to install and load PlutoTest
local_env = mktempdir()
Pkg.activate(local_env)
Pkg.add(name="PlutoTest", version="0.2")
pushfirst!(LOAD_PATH, local_env)

# activate Pluto's environment, used to load HTTP.jl
Pkg.activate(Base.current_project(@__FILE__))
using PlutoTest
else
if !isdefined(@__MODULE__, Symbol("@test"))
macro test(e...) nothing; end
macro test_throws(e...) nothing; end
macro test_broken(e...) nothing; end
macro testset(e...) nothing; end
end
end
import HTTP.URIs
import Base64
end

# ╔═║ a85c0c0b-47d0-4377-bc22-3c87239a67b3
"""
```julia
download_cool(url::AbstractString, [path::AbstractString = tempname()]) -> path
```
The same as [`Base.download`](@ref), but also supports [Data URLs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs).
"""
function download_cool(url::AbstractString, path::AbstractString=tempname())
if startswith(url, "data:")
comma_index = findfirst(',', url)

@assert comma_index isa Int "Invalid data URL."

metadata_str = url[length("data:")+1:comma_index-1]
metadata_parts = split(metadata_str, ';'; limit=2)

if length(metadata_parts) == 2
@assert metadata_parts[2] == "base64" "Invalid data URL."
end

mime = MIME(first(metadata_parts))
is_base64 = length(metadata_parts) == 2

data_str = SubString(url, comma_index+1)

data = is_base64 ?
Base64.base64decode(data_str) :
URIs.unescapeuri(data_str)

write(path, data)
path
else
download(url, path)
end
end

# ╔═║ 6e1dd79c-a7bf-44d6-bfa6-ced75b45170a
download_cool_string(args...) = read(download_cool(args...), String)

# ╔═║ 3630b4bc-ff63-426d-b95d-ae4e4f9ccd88
download_cool_data(args...) = read(download_cool(args...))

# ╔═║ 6339496d-11be-40d0-b4e5-9247e5199367
@test download_cool_string("data:,Hello%2C%20World%21") == "Hello, World!"

# ╔═║ bf7b4241-9cb0-4d90-9ded-b527bf220803
@test download_cool_string("data:text/plain,Hello%2C%20World%21") == "Hello, World!"

# ╔═║ d6e01532-a8e4-4173-a270-eae37c8002c7
@test download_cool_string("data:text/plain;base64,SGVsbG8sIFdvcmxkIQ==") == "Hello, World!"

# ╔═║ b0ba1add-f452-4a44-ab23-becbc610e2b9
@test download_cool_string("data:;base64,SGVsbG8sIFdvcmxkIQ==") == "Hello, World!"

# ╔═║ e630e261-1c2d-4117-9c44-dd49199fa3de
@test download_cool_string("data:,hello") == "hello"

# ╔═║ 4bb75573-09bd-4ce7-b76f-34c0249d7b88
@test download_cool_string("data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E") == "<h1>Hello, World!</h1>"

# ╔═║ 301eee81-7715-4d39-89aa-37bffde3557f
@test download_cool_string("data:text/html,<script>alert('hi');</script>") == "<script>alert('hi');</script>"

# ╔═║ ae296e09-08dd-4ee8-87ac-eb2bf24b28b9
random_data_url = "data:asf;base64,$(
Base64.base64encode(random_data)
)"

# ╔═║ 2eabfa58-2d8f-4479-9c00-a58b934638d9
@test download_cool_data(random_data_url) == random_data

# ╔═║ 525b2cb6-b7b9-436e-898e-a951e6a1f2f1
@test occursin("reactive", download_cool_string("https://raw.githubusercontent.com/fonsp/Pluto.jl/v0.17.1/README.md"))

# ╔═║ 40b48818-e191-4509-85ad-b9ff745cd0cb
@test_throws Exception download_cool("data:xoxo;base10,asdfasdfasdf")

# ╔═║ 1f175fcd-8b94-4f13-a912-02a21c95f8ca
@test_throws Exception download_cool("data:text/plain;base10,asdfasdfasdf")

# ╔═║ a4f671e6-0e23-4753-9301-048b2ef505e3
@test_throws Exception download_cool("data:asdfasdfasdf")

# ╔═║ Cell order:
# β•Ÿβ”€cc180e7e-46c3-11ec-3fff-05e1b5c77986
# ╠═a85c0c0b-47d0-4377-bc22-3c87239a67b3
# ╠═6339496d-11be-40d0-b4e5-9247e5199367
# ╠═bf7b4241-9cb0-4d90-9ded-b527bf220803
# ╠═d6e01532-a8e4-4173-a270-eae37c8002c7
# ╠═b0ba1add-f452-4a44-ab23-becbc610e2b9
# ╠═e630e261-1c2d-4117-9c44-dd49199fa3de
# ╠═4bb75573-09bd-4ce7-b76f-34c0249d7b88
# ╠═301eee81-7715-4d39-89aa-37bffde3557f
# ╠═2385dd3b-15f8-4790-907f-e0576a56c4c0
# ╠═ae296e09-08dd-4ee8-87ac-eb2bf24b28b9
# ╠═2eabfa58-2d8f-4479-9c00-a58b934638d9
# ╠═525b2cb6-b7b9-436e-898e-a951e6a1f2f1
# ╠═6e1dd79c-a7bf-44d6-bfa6-ced75b45170a
# ╠═3630b4bc-ff63-426d-b95d-ae4e4f9ccd88
# ╠═40b48818-e191-4509-85ad-b9ff745cd0cb
# ╠═1f175fcd-8b94-4f13-a912-02a21c95f8ca
# ╠═a4f671e6-0e23-4753-9301-048b2ef505e3
# ╠═d8ed6d44-33cd-4c9d-828b-d237d43769f5
# β•Ÿβ”€e1610184-5d16-499b-883e-7ef92f402ebb
# ╠═b987a8a2-6ab0-4e88-af3c-d7f2778af657
34 changes: 17 additions & 17 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
include("./helpers.jl")

# tests that start new processes:
include("./WorkspaceManager.jl")
include("./packages/Basic.jl")
include("./Bonds.jl")
VERSION > v"1.6.99" || include("./RichOutput.jl")
include("./React.jl")
include("./Dynamic.jl")
include("./MacroAnalysis.jl")
# include("./WorkspaceManager.jl")
# include("./packages/Basic.jl")
# include("./Bonds.jl")
# VERSION > v"1.6.99" || include("./RichOutput.jl")
# include("./React.jl")
# include("./Dynamic.jl")
# include("./MacroAnalysis.jl")

# for SOME reason 😞 the Notebook.jl tests need to run AFTER all the tests above, or the Github Actions runner on Windows gets internal julia errors.
include("./Notebook.jl")
# # for SOME reason 😞 the Notebook.jl tests need to run AFTER all the tests above, or the Github Actions runner on Windows gets internal julia errors.
# include("./Notebook.jl")

# tests that don't start new processes:
include("./ReloadFromFile.jl")
include("./packages/PkgCompat.jl")
include("./ExpressionExplorer.jl")
include("./MethodSignatures.jl")
VERSION > v"1.6.99" || include("./Configuration.jl")
include("./Analysis.jl")
include("./Firebasey.jl")
# # tests that don't start new processes:
# include("./ReloadFromFile.jl")
# include("./packages/PkgCompat.jl")
# include("./ExpressionExplorer.jl")
# include("./MethodSignatures.jl")
# VERSION > v"1.6.99" || include("./Configuration.jl")
# include("./Analysis.jl")
include("./webserver_utils.jl")
include("./DependencyCache.jl")
include("./Throttled.jl")
include("./cell_disabling.jl")
Expand Down
8 changes: 8 additions & 0 deletions test/Firebasey.jl β†’ test/webserver_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ module FirebaseyTestPlace end
include("../src/webserver/Firebasey.jl")
end)
end

module DataUrlTestPlace end
@testset "DataUrl" begin
Core.eval(DataUrlTestPlace, quote
using Test
include("../src/webserver/data_url.jl")
end)
end

0 comments on commit fecd831

Please sign in to comment.