Towtow10

Firewolf 3.5

Jan 26th, 2016
3,455
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 76.85 KB | None | 0 0
  1.  
  2. --
  3. --  Firewolf
  4. --  Made by GravityScore and 1lann and Towtow10
  5. --
  6.  
  7.  
  8.  
  9. --    Variables
  10.  
  11. local startupSite = "autoupdate"
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23. local version = "3.5"
  24. local build = 20
  25.  
  26. local w, h = term.getSize()
  27.  
  28. local isMenubarOpen = true
  29. local menubarWindow = nil
  30.  
  31. local allowUnencryptedConnections = true
  32. local enableTabBar = true
  33.  
  34. local currentWebsiteURL = ""
  35. local builtInSites = {}
  36.  
  37. local currentProtocol = ""
  38. local protocols = {}
  39.  
  40. local currentTab = 1
  41. local maxTabs = 5
  42. local maxTabNameWidth = 8
  43. local tabs = {}
  44.  
  45. local languages = {}
  46.  
  47. local history = {}
  48.  
  49. local publicDNSChannel = 9999
  50. local publicResponseChannel = 9998
  51. local responseID = 41738
  52.  
  53. local httpTimeout = 10
  54. local searchResultTimeout = 1
  55. local initiationTimeout = 2
  56. local animationInterval = 0.125
  57. local fetchTimeout = 3
  58. local serverLimitPerComputer = 1
  59.  
  60. local websiteErrorEvent = "firewolf_websiteErrorEvent"
  61. local redirectEvent = "firewolf_redirectEvent"
  62.  
  63. local baseURL = "https://raw.githubusercontent.com/Towtow10/Firewolf/master/src"
  64. local buildURL = baseURL .. "/build.txt"
  65. local firewolfURL = baseURL .. "/client.lua"
  66. local serverURL = baseURL .. "/server.lua"
  67.  
  68. local originalTerminal = term.current()
  69.  
  70. local firewolfLocation = "/" .. shell.getRunningProgram()
  71. local downloadsLocation = "/downloads"
  72.  
  73.  
  74. local theme = {}
  75.  
  76. local colorTheme = {
  77.     background = colors.gray,
  78.     accent = colors.red,
  79.     subtle = colors.orange,
  80.  
  81.     lightText = colors.gray,
  82.     text = colors.white,
  83.     errorText = colors.red,
  84. }
  85.  
  86. local grayscaleTheme = {
  87.     background = colors.black,
  88.     accent = colors.black,
  89.     subtle = colors.black,
  90.  
  91.     lightText = colors.white,
  92.     text = colors.white,
  93.     errorText = colors.white,
  94. }
  95.  
  96.  
  97.  
  98. --    Utilities
  99.  
  100.  
  101. local modifiedRead = function(properties)
  102.     local text = ""
  103.     local startX, startY = term.getCursorPos()
  104.     local pos = 0
  105.  
  106.     local previousText = ""
  107.     local readHistory = nil
  108.     local historyPos = 0
  109.  
  110.     if not properties then
  111.         properties = {}
  112.     end
  113.  
  114.     if properties.displayLength then
  115.         properties.displayLength = math.min(properties.displayLength, w - 2)
  116.     else
  117.         properties.displayLength = w - startX - 1
  118.     end
  119.  
  120.     if properties.startingText then
  121.         text = properties.startingText
  122.         pos = text:len()
  123.     end
  124.  
  125.     if properties.history then
  126.         readHistory = {}
  127.         for k, v in pairs(properties.history) do
  128.             readHistory[k] = v
  129.         end
  130.     end
  131.  
  132.     if readHistory[1] == text then
  133.         table.remove(readHistory, 1)
  134.     end
  135.  
  136.     local draw = function(replaceCharacter)
  137.         local scroll = 0
  138.         if properties.displayLength and pos > properties.displayLength then
  139.             scroll = pos - properties.displayLength
  140.         end
  141.  
  142.         local repl = replaceCharacter or properties.replaceCharacter
  143.         term.setTextColor(theme.text)
  144.         term.setCursorPos(startX, startY)
  145.         if repl then
  146.             term.write(string.rep(repl:sub(1, 1), text:len() - scroll))
  147.         else
  148.             term.write(text:sub(scroll + 1))
  149.         end
  150.  
  151.         term.setCursorPos(startX + pos - scroll, startY)
  152.     end
  153.  
  154.     term.setCursorBlink(true)
  155.     draw()
  156.     while true do
  157.         local event, key, x, y, param4, param5 = os.pullEvent()
  158.  
  159.         if properties.onEvent then
  160.             -- Actions:
  161.             -- - exit (bool)
  162.             -- - text
  163.             -- - nullifyText
  164.  
  165.             term.setCursorBlink(false)
  166.             local action = properties.onEvent(text, event, key, x, y, param4, param5)
  167.             if action then
  168.                 if action.text then
  169.                     draw(" ")
  170.                     text = action.text
  171.                     pos = text:len()
  172.                 end if action.nullifyText then
  173.                     text = nil
  174.                     action.exit = true
  175.                 end if action.exit then
  176.                     break
  177.                 end
  178.             end
  179.             draw()
  180.         end
  181.  
  182.         term.setCursorBlink(true)
  183.         if event == "char" then
  184.             local canType = true
  185.             if properties.maxLength and text:len() >= properties.maxLength then
  186.                 canType = false
  187.             end
  188.  
  189.             if canType then
  190.                 text = text:sub(1, pos) .. key .. text:sub(pos + 1, -1)
  191.                 pos = pos + 1
  192.                 draw()
  193.             end
  194.         elseif event == "key" then
  195.             if key == keys.enter then
  196.                 break
  197.             elseif key == keys.left and pos > 0 then
  198.                 pos = pos - 1
  199.                 draw()
  200.             elseif key == keys.right and pos < text:len() then
  201.                 pos = pos + 1
  202.                 draw()
  203.             elseif key == keys.backspace and pos > 0 then
  204.                 draw(" ")
  205.                 text = text:sub(1, pos - 1) .. text:sub(pos + 1, -1)
  206.                 pos = pos - 1
  207.                 draw()
  208.             elseif key == keys.delete and pos < text:len() then
  209.                 draw(" ")
  210.                 text = text:sub(1, pos) .. text:sub(pos + 2, -1)
  211.                 draw()
  212.             elseif key == keys.home then
  213.                 pos = 0
  214.                 draw()
  215.             elseif key == keys["end"] then
  216.                 pos = text:len()
  217.                 draw()
  218.             elseif (key == keys.up or key == keys.down) and readHistory then
  219.                 local shouldDraw = false
  220.                 if historyPos == 0 then
  221.                     previousText = text
  222.                 elseif historyPos > 0 then
  223.                     readHistory[historyPos] = text
  224.                 end
  225.  
  226.                 if key == keys.up then
  227.                     if historyPos < #readHistory then
  228.                         historyPos = historyPos + 1
  229.                         shouldDraw = true
  230.                     end
  231.                 else
  232.                     if historyPos > 0 then
  233.                         historyPos = historyPos - 1
  234.                         shouldDraw = true
  235.                     end
  236.                 end
  237.  
  238.                 if shouldDraw then
  239.                     draw(" ")
  240.                     if historyPos > 0 then
  241.                         text = readHistory[historyPos]
  242.                     else
  243.                         text = previousText
  244.                     end
  245.                     pos = text:len()
  246.                     draw()
  247.                 end
  248.             end
  249.         elseif event == "mouse_click" then
  250.             local scroll = 0
  251.             if properties.displayLength and pos > properties.displayLength then
  252.                 scroll = pos - properties.displayLength
  253.             end
  254.  
  255.             if y == startY and x >= startX and x <= math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  256.                 pos = x - startX + scroll
  257.                 draw()
  258.             elseif y == startY then
  259.                 if x < startX then
  260.                     pos = scroll
  261.                     draw()
  262.                 elseif x > math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then
  263.                     pos = text:len()
  264.                     draw()
  265.                 end
  266.             end
  267.         end
  268.     end
  269.  
  270.     term.setCursorBlink(false)
  271.     print("")
  272.     return text
  273. end
  274.  
  275.  
  276. local prompt = function(items, x, y, w, h)
  277.     local selected = 1
  278.     local scroll = 0
  279.  
  280.     local draw = function()
  281.         for i = scroll + 1, scroll + h do
  282.             local item = items[i]
  283.             if item then
  284.                 term.setCursorPos(x, y + i - 1)
  285.                 term.setBackgroundColor(theme.background)
  286.                 term.setTextColor(theme.lightText)
  287.  
  288.                 if scroll + selected == i then
  289.                     term.setTextColor(theme.text)
  290.                     term.write(" > ")
  291.                 else
  292.                     term.write(" - ")
  293.                 end
  294.  
  295.                 term.write(item)
  296.             end
  297.         end
  298.     end
  299.  
  300.     draw()
  301.     while true do
  302.         local event, key, x, y = os.pullEvent()
  303.  
  304.         if event == "key" then
  305.             if key == keys.up and selected > 1 then
  306.                 selected = selected - 1
  307.  
  308.                 if selected - scroll == 0 then
  309.                     scroll = scroll - 1
  310.                 end
  311.             elseif key == keys.down and selected < #items then
  312.                 selected = selected + 1
  313.             end
  314.  
  315.             draw()
  316.         elseif event == "mouse_click" then
  317.  
  318.         elseif event == "mouse_scroll" then
  319.             if key > 0 then
  320.                 os.queueEvent("key", keys.down)
  321.             else
  322.                 os.queueEvent("key", keys.up)
  323.             end
  324.         end
  325.     end
  326. end
  327.  
  328.  
  329.  
  330. --    GUI
  331.  
  332.  
  333. local clear = function(bg, fg)
  334.     term.setTextColor(fg)
  335.     term.setBackgroundColor(bg)
  336.     term.clear()
  337.     term.setCursorPos(1, 1)
  338. end
  339.  
  340.  
  341. local fill = function(x, y, width, height, bg)
  342.     term.setBackgroundColor(bg)
  343.     for i = y, y + height - 1 do
  344.         term.setCursorPos(x, i)
  345.         term.write(string.rep(" ", width))
  346.     end
  347. end
  348.  
  349.  
  350. local center = function(text)
  351.     local x, y = term.getCursorPos()
  352.     term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y)
  353.     term.write(text)
  354.     term.setCursorPos(1, y + 1)
  355. end
  356.  
  357.  
  358. local centerSplit = function(text, width)
  359.     local words = {}
  360.     for word in text:gmatch("[^ \t]+") do
  361.         table.insert(words, word)
  362.     end
  363.  
  364.     local lines = {""}
  365.     while lines[#lines]:len() < width do
  366.         lines[#lines] = lines[#lines] .. words[1] .. " "
  367.         table.remove(words, 1)
  368.  
  369.         if #words == 0 then
  370.             break
  371.         end
  372.  
  373.         if lines[#lines]:len() + words[1]:len() >= width then
  374.             table.insert(lines, "")
  375.         end
  376.     end
  377.  
  378.     for _, line in pairs(lines) do
  379.         center(line)
  380.     end
  381. end
  382.  
  383.  
  384.  
  385. --    Updating
  386.  
  387.  
  388. local download = function(url)
  389.     http.request(url)
  390.     local timeoutID = os.startTimer(httpTimeout)
  391.     while true do
  392.         local event, fetchedURL, response = os.pullEvent()
  393.         if (event == "timer" and fetchedURL == timeoutID) or event == "http_failure" then
  394.             return false
  395.         elseif event == "http_success" and fetchedURL == url then
  396.             local contents = response.readAll()
  397.             response.close()
  398.             return contents
  399.         end
  400.     end
  401. end
  402.  
  403.  
  404. local downloadAndSave = function(url, path)
  405.     local contents = download(url)
  406.     if contents and not fs.isReadOnly(path) and not fs.isDir(path) then
  407.         local f = io.open(path, "w")
  408.         f:write(contents)
  409.         f:close()
  410.         return false
  411.     end
  412.     return true
  413. end
  414.  
  415.  
  416. local updateAvailable = function()
  417.     local number = download(buildURL)
  418.     if not number then
  419.         return false, true
  420.     end
  421.  
  422.     if number and tonumber(number) and tonumber(number) > build then
  423.         return true, false
  424.     end
  425.  
  426.     return false, false
  427. end
  428.  
  429.  
  430. local redownloadBrowser = function()
  431.     return downloadAndSave(firewolfURL, firewolfLocation)
  432. end
  433.  
  434.  
  435.  
  436. --    Display Websites
  437.  
  438.  
  439. builtInSites["display"] = {}
  440.  
  441.  
  442. builtInSites["display"]["firewolf"] = function()
  443.     local logo = {
  444.         "______                         _  __ ",
  445.         "|  ___|                       | |/ _|",
  446.         "| |_  _ ____ _____      _____ | | |_ ",
  447.         "|  _|| |  __/ _ \\ \\ /\\ / / _ \\| |  _|",
  448.         "| |  | | | |  __/\\ V  V / <_> | | |  ",
  449.         "\\_|  |_|_|  \\___| \\_/\\_/ \\___/|_|_|  ",
  450.     }
  451.  
  452.     clear(theme.background, theme.text)
  453.     fill(1, 3, w, 9, theme.subtle)
  454.  
  455.     term.setCursorPos(1, 3)
  456.     for _, line in pairs(logo) do
  457.         center(line)
  458.     end
  459.  
  460.     term.setCursorPos(1, 10)
  461.     center(version)
  462.  
  463.     term.setBackgroundColor(theme.background)
  464.     term.setTextColor(theme.text)
  465.     term.setCursorPos(1, 14)
  466.     center("Search using the Query Box above")
  467.     center("Visit rdnt://help for help using Firewolf.")
  468.  
  469.     term.setCursorPos(1, h - 2)
  470.     center("Made by GravityScore and 1lann and Towtow10")
  471. end
  472.  
  473.  
  474. builtInSites["display"]["credits"] = function()
  475.     clear(theme.background, theme.text)
  476.  
  477.     fill(1, 6, w, 3, theme.subtle)
  478.     term.setCursorPos(1, 7)
  479.     center("Credits")
  480.  
  481.     term.setBackgroundColor(theme.background)
  482.     term.setCursorPos(1, 11)
  483.     center("Written by GravityScore and 1lann and Towtow10")
  484.     print("")
  485.     center("RC4 Implementation by AgentE382")
  486. end
  487.  
  488.  
  489. builtInSites["display"]["help"] = function()
  490.     clear(theme.background, theme.text)
  491.  
  492.     fill(1, 3, w, 3, theme.subtle)
  493.     term.setCursorPos(1, 4)
  494.     center("Help")
  495.  
  496.     term.setBackgroundColor(theme.background)
  497.     term.setCursorPos(1, 7)
  498.     center("Click on the URL bar or press control to")
  499.     center("open the query box")
  500.     print("")
  501.     center("Type in a search query or website URL")
  502.     center("into the query box.")
  503.     print("")
  504.     center("Search for nothing to see all available")
  505.     center("websites.")
  506.     print("")
  507.     center("Visit rdnt://server to setup a server.")
  508.     center("Visit rdnt://update to update Firewolf.")
  509. end
  510.  
  511.  
  512. builtInSites["display"]["server"] = function()
  513.     clear(theme.background, theme.text)
  514.  
  515.     fill(1, 6, w, 3, theme.subtle)
  516.     term.setCursorPos(1, 7)
  517.     center("Server Software")
  518.  
  519.     term.setBackgroundColor(theme.background)
  520.     term.setCursorPos(1, 11)
  521.     if not http then
  522.         center("HTTP is not enabled!")
  523.         print("")
  524.         center("Please enable it in your config file")
  525.         center("to download Firewolf Server.")
  526.     else
  527.         center("Press space to download")
  528.         center("Firewolf Server to:")
  529.         print("")
  530.         center("/fwserver")
  531.  
  532.         while true do
  533.             local event, key = os.pullEvent()
  534.             if event == "key" and key == 57 then
  535.                 fill(1, 11, w, 4, theme.background)
  536.                 term.setCursorPos(1, 11)
  537.                 center("Downloading...")
  538.  
  539.                 local err = downloadAndSave(serverURL, "/fwserver")
  540.  
  541.                 fill(1, 11, w, 4, theme.background)
  542.                 term.setCursorPos(1, 11)
  543.                 center(err and "Download failed!" or "Download successful!")
  544.             end
  545.         end
  546.     end
  547. end
  548.  
  549.  
  550. builtInSites["display"]["update"] = function()
  551.     clear(theme.background, theme.text)
  552.  
  553.     fill(1, 3, w, 3, theme.subtle)
  554.     term.setCursorPos(1, 4)
  555.     center("Update")
  556.  
  557.     term.setBackgroundColor(theme.background)
  558.     if not http then
  559.         term.setCursorPos(1, 9)
  560.         center("HTTP is not enabled!")
  561.         print("")
  562.         center("Please enable it in your config")
  563.         center("file to download Firewolf updates.")
  564.     else
  565.         term.setCursorPos(1, 10)
  566.         center("Checking for updates...")
  567.  
  568.         local available, err = updateAvailable()
  569.  
  570.         term.setCursorPos(1, 10)
  571.         if available then
  572.             term.clearLine()
  573.             center("Update found!")
  574.             center("Press enter to download.")
  575.  
  576.             while true do
  577.                 local event, key = os.pullEvent()
  578.                 if event == "key" and key == keys.enter then
  579.                     break
  580.                 end
  581.             end
  582.  
  583.             fill(1, 10, w, 2, theme.background)
  584.             term.setCursorPos(1, 10)
  585.             center("Downloading...")
  586.  
  587.             local err = redownloadBrowser()
  588.  
  589.             term.setCursorPos(1, 10)
  590.             term.clearLine()
  591.             if err then
  592.                 center("Download failed!")
  593.             else
  594.                 center("Download succeeded!")
  595.                 center("Please restart Firewolf...")
  596.             end
  597.         elseif err then
  598.             term.clearLine()
  599.             center("Checking failed!")
  600.         else
  601.             term.clearLine()
  602.             center("No updates found.")
  603.         end
  604.     end
  605. end
  606.  
  607.  
  608.  
  609.  
  610. builtInSites["display"]["autoupdate"] = function()
  611.     clear(theme.background, theme.text)
  612.  
  613.     fill(1, 3, w, 3, theme.subtle)
  614.     term.setCursorPos(1, 4)
  615.     center("Update")
  616.  
  617.     term.setBackgroundColor(theme.background)
  618.     if not http then
  619.         term.setCursorPos(1, 9)
  620.         center("HTTP is not enabled!")
  621.         print("")
  622.         center("Please enable it in your config")
  623.         center("file to download Firewolf updates.")
  624.     else
  625.         term.setCursorPos(1, 10)
  626.         center("Checking for updates...")
  627.  
  628.         local available, err = updateAvailable()
  629.  
  630.         term.setCursorPos(1, 10)
  631.         if available then
  632.             term.clearLine()
  633.             center("Update found!")
  634.  
  635.             fill(1, 10, w, 2, theme.background)
  636.             term.setCursorPos(1, 10)
  637.             center("Downloading...")
  638.  
  639.             local err = redownloadBrowser()
  640.  
  641.             term.setCursorPos(1, 10)
  642.             term.clearLine()
  643.             if err then
  644.                 center("Download failed!")
  645.             else
  646.                 center("Download succeeded!")
  647.                 center("Please restart Firewolf...")
  648.             end
  649.         elseif err then
  650.             term.clearLine()
  651.             center("Checking failed!")
  652.         else
  653.             term.clearLine()
  654.             center("No updates found.")
  655.         end
  656.     end
  657.     firewolf.redirect("firewolf")
  658. end
  659.  
  660.  
  661.  
  662.  
  663.  
  664.  
  665.  
  666. --    Built In Websites
  667.  
  668.  
  669. builtInSites["error"] = function(err)
  670.     fill(1, 3, w, 3, theme.subtle)
  671.     term.setCursorPos(1, 4)
  672.     center("Failed to load page!")
  673.  
  674.     term.setBackgroundColor(theme.background)
  675.     term.setCursorPos(1, 9)
  676.     center(err)
  677.     print("")
  678.     center("Please try again.")
  679. end
  680.  
  681.  
  682. builtInSites["noresults"] = function()
  683.     fill(1, 3, w, 3, theme.subtle)
  684.     term.setCursorPos(1, 4)
  685.     center("No results!")
  686.  
  687.     term.setBackgroundColor(theme.background)
  688.     term.setCursorPos(1, 9)
  689.     center("Your search didn't return")
  690.     center("any results!")
  691.  
  692.     os.pullEvent("key")
  693.     os.queueEvent("")
  694.     os.pullEvent()
  695. end
  696.  
  697.  
  698. builtInSites["search advanced"] = function(results)
  699.     local startY = 6
  700.     local height = h - startY - 1
  701.     local scroll = 0
  702.  
  703.     local draw = function()
  704.         fill(1, startY, w, height + 1, theme.background)
  705.  
  706.         for i = scroll + 1, scroll + height do
  707.             if results[i] then
  708.                 term.setCursorPos(5, (i - scroll) + startY)
  709.                 term.write(currentProtocol .. "://" .. results[i])
  710.             end
  711.         end
  712.     end
  713.  
  714.     draw()
  715.     while true do
  716.         local event, but, x, y = os.pullEvent()
  717.  
  718.         if event == "mouse_click" and y >= startY and y <= startY + height then
  719.             local item = results[y - startY + scroll]
  720.             if item then
  721.                 os.queueEvent(redirectEvent, item)
  722.                 coroutine.yield()
  723.             end
  724.         elseif event == "key" then
  725.             if but == keys.up then
  726.                 scroll = math.max(0, scroll - 1)
  727.             elseif but == keys.down and #results > height then
  728.                 scroll = math.min(scroll + 1, #results - height)
  729.             end
  730.  
  731.             draw()
  732.         elseif event == "mouse_scroll" then
  733.             if but > 0 then
  734.                 os.queueEvent("key", keys.down)
  735.             else
  736.                 os.queueEvent("key", keys.up)
  737.             end
  738.         end
  739.     end
  740. end
  741.  
  742.  
  743. builtInSites["search basic"] = function(results)
  744.     local startY = 6
  745.     local height = h - startY - 1
  746.     local scroll = 0
  747.     local selected = 1
  748.  
  749.     local draw = function()
  750.         fill(1, startY, w, height + 1, theme.background)
  751.  
  752.         for i = scroll + 1, scroll + height do
  753.             if results[i] then
  754.                 if i == selected + scroll then
  755.                     term.setCursorPos(3, (i - scroll) + startY)
  756.                     term.write("> " .. currentProtocol .. "://" .. results[i])
  757.                 else
  758.                     term.setCursorPos(5, (i - scroll) + startY)
  759.                     term.write(currentProtocol .. "://" .. results[i])
  760.                 end
  761.             end
  762.         end
  763.     end
  764.  
  765.     draw()
  766.     while true do
  767.         local event, but, x, y = os.pullEvent()
  768.  
  769.         if event == "key" then
  770.             if but == keys.up and selected + scroll > 1 then
  771.                 if selected > 1 then
  772.                     selected = selected - 1
  773.                 else
  774.                     scroll = math.max(0, scroll - 1)
  775.                 end
  776.             elseif but == keys.down and selected + scroll < #results then
  777.                 if selected < height then
  778.                     selected = selected + 1
  779.                 else
  780.                     scroll = math.min(scroll + 1, #results - height)
  781.                 end
  782.             elseif but == keys.enter then
  783.                 local item = results[scroll + selected]
  784.                 if item then
  785.                     os.queueEvent(redirectEvent, item)
  786.                     coroutine.yield()
  787.                 end
  788.             end
  789.  
  790.             draw()
  791.         elseif event == "mouse_scroll" then
  792.             if but > 0 then
  793.                 os.queueEvent("key", keys.down)
  794.             else
  795.                 os.queueEvent("key", keys.up)
  796.             end
  797.         end
  798.     end
  799. end
  800.  
  801.  
  802. builtInSites["search"] = function(results)
  803.     clear(theme.background, theme.text)
  804.  
  805.     fill(1, 3, w, 3, theme.subtle)
  806.     term.setCursorPos(1, 4)
  807.     center(#results .. " Search " .. (#results == 1 and "Result" or "Results"))
  808.  
  809.     term.setBackgroundColor(theme.background)
  810.  
  811.     if term.isColor() then
  812.         builtInSites["search advanced"](results)
  813.     else
  814.         builtInSites["search basic"](results)
  815.     end
  816. end
  817.  
  818.  
  819. builtInSites["crash"] = function(err)
  820.     fill(1, 3, w, 3, theme.subtle)
  821.     term.setCursorPos(1, 4)
  822.     center("The website crashed!")
  823.  
  824.     term.setBackgroundColor(theme.background)
  825.     term.setCursorPos(1, 8)
  826.     centerSplit(err, w - 4)
  827.     print("\n")
  828.     center("Please report this error to")
  829.     center("the website creator.")
  830. end
  831.  
  832.  
  833.  
  834. --    Menubar
  835.  
  836.  
  837. local getTabName = function(url)
  838.     local name = url:match("^[^/]+")
  839.  
  840.     if not name then
  841.         name = "Search"
  842.     end
  843.  
  844.     if name:sub(1, 3) == "www" then
  845.         name = name:sub(5):gsub("^%s*(.-)%s*$", "%1")
  846.     end
  847.  
  848.     if name:len() > maxTabNameWidth then
  849.         name = name:sub(1, maxTabNameWidth):gsub("^%s*(.-)%s*$", "%1")
  850.     end
  851.  
  852.     if name:sub(-1, -1) == "." then
  853.         name = name:sub(1, -2):gsub("^%s*(.-)%s*$", "%1")
  854.     end
  855.  
  856.     return name:gsub("^%s*(.-)%s*$", "%1")
  857. end
  858.  
  859.  
  860. local determineClickedTab = function(x, y)
  861.     if y == 2 then
  862.         local minx = 2
  863.         for i, tab in pairs(tabs) do
  864.             local name = getTabName(tab.url)
  865.  
  866.             if x >= minx and x <= minx + name:len() - 1 then
  867.                 return i
  868.             elseif x == minx + name:len() and i == currentTab and #tabs > 1 then
  869.                 return "close"
  870.             else
  871.                 minx = minx + name:len() + 2
  872.             end
  873.         end
  874.  
  875.         if x == minx and #tabs < maxTabs then
  876.             return "new"
  877.         end
  878.     end
  879.  
  880.     return nil
  881. end
  882.  
  883.  
  884. local setupMenubar = function()
  885.     if enableTabBar then
  886.         menubarWindow = window.create(originalTerminal, 1, 1, w, 2, false)
  887.     else
  888.         menubarWindow = window.create(originalTerminal, 1, 1, w, 1, false)
  889.     end
  890. end
  891.  
  892.  
  893. local drawMenubar = function()
  894.     if isMenubarOpen then
  895.         term.redirect(menubarWindow)
  896.         menubarWindow.setVisible(true)
  897.  
  898.         fill(1, 1, w, 1, theme.accent)
  899.         term.setTextColor(theme.text)
  900.  
  901.         term.setBackgroundColor(theme.accent)
  902.         term.setCursorPos(2, 1)
  903.         if currentWebsiteURL:match("^[^%?]+") then
  904.             term.write(currentProtocol .. "://" .. currentWebsiteURL:match("^[^%?]+"))
  905.         else
  906.             term.write(currentProtocol .. "://" ..currentWebsiteURL)
  907.         end
  908.  
  909.         term.setCursorPos(w - 5, 1)
  910.         term.write("[===]")
  911.  
  912.         if enableTabBar then
  913.             fill(1, 2, w, 1, theme.subtle)
  914.  
  915.             term.setCursorPos(1, 2)
  916.             for i, tab in pairs(tabs) do
  917.                 term.setBackgroundColor(theme.subtle)
  918.                 term.setTextColor(theme.lightText)
  919.                 if i == currentTab then
  920.                     term.setTextColor(theme.text)
  921.                 end
  922.  
  923.                 local tabName = getTabName(tab.url)
  924.                 term.write(" " .. tabName)
  925.  
  926.                 if i == currentTab and #tabs > 1 then
  927.                     term.setTextColor(theme.errorText)
  928.                     term.write("x")
  929.                 else
  930.                     term.write(" ")
  931.                 end
  932.             end
  933.  
  934.             if #tabs < maxTabs then
  935.                 term.setTextColor(theme.lightText)
  936.                 term.setBackgroundColor(theme.subtle)
  937.                 term.write(" + ")
  938.             end
  939.         end
  940.     else
  941.         menubarWindow.setVisible(false)
  942.     end
  943. end
  944.  
  945.  
  946.  
  947. --  RC4
  948. --  Implementation by AgentE382
  949.  
  950.  
  951. local cryptWrapper = function(plaintext, salt)
  952.     local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt, 1, #salt)}
  953.     local S = {}
  954.     for i = 0, 255 do
  955.         S[i] = i
  956.     end
  957.  
  958.     local j, keylength = 0, #key
  959.     for i = 0, 255 do
  960.         j = (j + S[i] + key[i % keylength + 1]) % 256
  961.         S[i], S[j] = S[j], S[i]
  962.     end
  963.  
  964.     local i = 0
  965.     j = 0
  966.     local chars, astable = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext, 1, #plaintext)}, false
  967.  
  968.     for n = 1, #chars do
  969.         i = (i + 1) % 256
  970.         j = (j + S[i]) % 256
  971.         S[i], S[j] = S[j], S[i]
  972.         chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n])
  973.         if chars[n] > 127 or chars[n] == 13 then
  974.             astable = true
  975.         end
  976.     end
  977.  
  978.     return astable and chars or string.char(unpack(chars))
  979. end
  980.  
  981.  
  982. local crypt = function(text, key)
  983.     local resp, msg = pcall(cryptWrapper, text, key)
  984.     if resp then
  985.         return msg
  986.     else
  987.         return nil
  988.     end
  989. end
  990.  
  991.  
  992.  
  993. --  Base64
  994. --
  995. --  Base64 Encryption/Decryption
  996. --  By KillaVanilla
  997. --  http://www.computercraft.info/forums2/index.php?/topic/12450-killavanillas-various-apis/
  998. --  http://pastebin.com/rCYDnCxn
  999. --
  1000.  
  1001.  
  1002. local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  1003.  
  1004.  
  1005. local function sixBitToBase64(input)
  1006.     return string.sub(alphabet, input+1, input+1)
  1007. end
  1008.  
  1009.  
  1010. local function base64ToSixBit(input)
  1011.     for i=1, 64 do
  1012.         if input == string.sub(alphabet, i, i) then
  1013.             return i-1
  1014.         end
  1015.     end
  1016. end
  1017.  
  1018.  
  1019. local function octetToBase64(o1, o2, o3)
  1020.     local shifted = bit.brshift(bit.band(o1, 0xFC), 2)
  1021.     local i1 = sixBitToBase64(shifted)
  1022.     local i2 = "A"
  1023.     local i3 = "="
  1024.     local i4 = "="
  1025.     if o2 then
  1026.         i2 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o1, 3), 4), bit.brshift(bit.band(o2, 0xF0), 4) ))
  1027.         if not o3 then
  1028.             i3 = sixBitToBase64(bit.blshift(bit.band(o2, 0x0F), 2))
  1029.         else
  1030.             i3 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o2, 0x0F), 2), bit.brshift(bit.band(o3, 0xC0), 6) ))
  1031.         end
  1032.     else
  1033.         i2 = sixBitToBase64(bit.blshift(bit.band(o1, 3), 4))
  1034.     end
  1035.     if o3 then
  1036.         i4 = sixBitToBase64(bit.band(o3, 0x3F))
  1037.     end
  1038.  
  1039.     return i1..i2..i3..i4
  1040. end
  1041.  
  1042.  
  1043. local function base64ToThreeOctet(s1)
  1044.     local c1 = base64ToSixBit(string.sub(s1, 1, 1))
  1045.     local c2 = base64ToSixBit(string.sub(s1, 2, 2))
  1046.     local c3 = 0
  1047.     local c4 = 0
  1048.     local o1 = 0
  1049.     local o2 = 0
  1050.     local o3 = 0
  1051.     if string.sub(s1, 3, 3) == "=" then
  1052.         c3 = nil
  1053.         c4 = nil
  1054.     elseif string.sub(s1, 4, 4) == "=" then
  1055.         c3 = base64ToSixBit(string.sub(s1, 3, 3))
  1056.         c4 = nil
  1057.     else
  1058.         c3 = base64ToSixBit(string.sub(s1, 3, 3))
  1059.         c4 = base64ToSixBit(string.sub(s1, 4, 4))
  1060.     end
  1061.     o1 = bit.bor( bit.blshift(c1, 2), bit.brshift(bit.band( c2, 0x30 ), 4) )
  1062.     if c3 then
  1063.         o2 = bit.bor( bit.blshift(bit.band(c2, 0x0F), 4), bit.brshift(bit.band( c3, 0x3C ), 2) )
  1064.     else
  1065.         o2 = nil
  1066.     end
  1067.     if c4 then
  1068.         o3 = bit.bor( bit.blshift(bit.band(c3, 3), 6), c4 )
  1069.     else
  1070.         o3 = nil
  1071.     end
  1072.     return o1, o2, o3
  1073. end
  1074.  
  1075.  
  1076. local function splitIntoBlocks(bytes)
  1077.     local blockNum = 1
  1078.     local blocks = {}
  1079.     for i=1, #bytes, 3 do
  1080.         blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]}
  1081.         blockNum = blockNum+1
  1082.     end
  1083.     return blocks
  1084. end
  1085.  
  1086.  
  1087. function base64Encode(bytes)
  1088.     local blocks = splitIntoBlocks(bytes)
  1089.     local output = ""
  1090.     for i=1, #blocks do
  1091.         output = output..octetToBase64( unpack(blocks[i]) )
  1092.     end
  1093.     return output
  1094. end
  1095.  
  1096.  
  1097. function base64Decode(str)
  1098.     local bytes = {}
  1099.     local blocks = {}
  1100.     local blockNum = 1
  1101.  
  1102.     for i=1, #str, 4 do
  1103.         blocks[blockNum] = string.sub(str, i, i+3)
  1104.         blockNum = blockNum+1
  1105.     end
  1106.  
  1107.     for i=1, #blocks do
  1108.         local o1, o2, o3 = base64ToThreeOctet(blocks[i])
  1109.         table.insert(bytes, o1)
  1110.         table.insert(bytes, o2)
  1111.         table.insert(bytes, o3)
  1112.     end
  1113.  
  1114.     return bytes
  1115. end
  1116.  
  1117.  
  1118.  
  1119. --  SHA-256
  1120. --
  1121. --  Adaptation of the Secure Hashing Algorithm (SHA-244/256)
  1122. --  Found Here: http://lua-users.org/wiki/SecureHashAlgorithm
  1123. --
  1124. --  Using an adapted version of the bit library
  1125. --  Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua
  1126.  
  1127.  
  1128. local MOD = 2^32
  1129. local MODM = MOD-1
  1130.  
  1131.  
  1132. local function memoize(f)
  1133.     local mt = {}
  1134.     local t = setmetatable({}, mt)
  1135.     function mt:__index(k)
  1136.         local v = f(k)
  1137.         t[k] = v
  1138.         return v
  1139.     end
  1140.     return t
  1141. end
  1142.  
  1143.  
  1144. local function make_bitop_uncached(t, m)
  1145.     local function bitop(a, b)
  1146.         local res,p = 0,1
  1147.         while a ~= 0 and b ~= 0 do
  1148.             local am, bm = a % m, b % m
  1149.             res = res + t[am][bm] * p
  1150.             a = (a - am) / m
  1151.             b = (b - bm) / m
  1152.             p = p * m
  1153.         end
  1154.         res = res + (a + b) * p
  1155.         return res
  1156.     end
  1157.  
  1158.     return bitop
  1159. end
  1160.  
  1161.  
  1162. local function make_bitop(t)
  1163.     local op1 = make_bitop_uncached(t,2^1)
  1164.     local op2 = memoize(function(a)
  1165.         return memoize(function(b)
  1166.             return op1(a, b)
  1167.         end)
  1168.     end)
  1169.     return make_bitop_uncached(op2, 2 ^ (t.n or 1))
  1170. end
  1171.  
  1172.  
  1173. local customBxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4})
  1174.  
  1175. local function customBxor(a, b, c, ...)
  1176.     local z = nil
  1177.     if b then
  1178.         a = a % MOD
  1179.         b = b % MOD
  1180.         z = customBxor1(a, b)
  1181.         if c then
  1182.             z = customBxor(z, c, ...)
  1183.         end
  1184.         return z
  1185.     elseif a then
  1186.         return a % MOD
  1187.     else
  1188.         return 0
  1189.     end
  1190. end
  1191.  
  1192.  
  1193. local function customBand(a, b, c, ...)
  1194.     local z
  1195.     if b then
  1196.         a = a % MOD
  1197.         b = b % MOD
  1198.         z = ((a + b) - customBxor1(a,b)) / 2
  1199.         if c then
  1200.             z = customBand(z, c, ...)
  1201.         end
  1202.         return z
  1203.     elseif a then
  1204.         return a % MOD
  1205.     else
  1206.         return MODM
  1207.     end
  1208. end
  1209.  
  1210.  
  1211. local function bnot(x)
  1212.     return (-1 - x) % MOD
  1213. end
  1214.  
  1215.  
  1216. local function rshift1(a, disp)
  1217.     if disp < 0 then
  1218.         return lshift(a, -disp)
  1219.     end
  1220.     return math.floor(a % 2 ^ 32 / 2 ^ disp)
  1221. end
  1222.  
  1223.  
  1224. local function rshift(x, disp)
  1225.     if disp > 31 or disp < -31 then
  1226.         return 0
  1227.     end
  1228.     return rshift1(x % MOD, disp)
  1229. end
  1230.  
  1231.  
  1232. local function lshift(a, disp)
  1233.     if disp < 0 then
  1234.         return rshift(a, -disp)
  1235.     end
  1236.     return (a * 2 ^ disp) % 2 ^ 32
  1237. end
  1238.  
  1239.  
  1240. local function rrotate(x, disp)
  1241.     x = x % MOD
  1242.     disp = disp % 32
  1243.     local low = customBand(x, 2 ^ disp - 1)
  1244.     return rshift(x, disp) + lshift(low, 32 - disp)
  1245. end
  1246.  
  1247.  
  1248. local k = {
  1249.     0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
  1250.     0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
  1251.     0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
  1252.     0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
  1253.     0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
  1254.     0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
  1255.     0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
  1256.     0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
  1257.     0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
  1258.     0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
  1259.     0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
  1260.     0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
  1261.     0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
  1262.     0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  1263.     0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
  1264.     0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
  1265. }
  1266.  
  1267.  
  1268. local function str2hexa(s)
  1269.     return (string.gsub(s, ".", function(c)
  1270.         return string.format("%02x", string.byte(c))
  1271.     end))
  1272. end
  1273.  
  1274.  
  1275. local function num2s(l, n)
  1276.     local s = ""
  1277.     for i = 1, n do
  1278.         local rem = l % 256
  1279.         s = string.char(rem) .. s
  1280.         l = (l - rem) / 256
  1281.     end
  1282.     return s
  1283. end
  1284.  
  1285.  
  1286. local function s232num(s, i)
  1287.     local n = 0
  1288.     for i = i, i + 3 do
  1289.         n = n*256 + string.byte(s, i)
  1290.     end
  1291.     return n
  1292. end
  1293.  
  1294.  
  1295. local function preproc(msg, len)
  1296.     local extra = 64 - ((len + 9) % 64)
  1297.     len = num2s(8 * len, 8)
  1298.     msg = msg .. "\128" .. string.rep("\0", extra) .. len
  1299.     assert(#msg % 64 == 0)
  1300.     return msg
  1301. end
  1302.  
  1303.  
  1304. local function initH256(H)
  1305.     H[1] = 0x6a09e667
  1306.     H[2] = 0xbb67ae85
  1307.     H[3] = 0x3c6ef372
  1308.     H[4] = 0xa54ff53a
  1309.     H[5] = 0x510e527f
  1310.     H[6] = 0x9b05688c
  1311.     H[7] = 0x1f83d9ab
  1312.     H[8] = 0x5be0cd19
  1313.     return H
  1314. end
  1315.  
  1316.  
  1317. local function digestblock(msg, i, H)
  1318.     local w = {}
  1319.     for j = 1, 16 do
  1320.         w[j] = s232num(msg, i + (j - 1)*4)
  1321.     end
  1322.     for j = 17, 64 do
  1323.         local v = w[j - 15]
  1324.         local s0 = customBxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3))
  1325.         v = w[j - 2]
  1326.         w[j] = w[j - 16] + s0 + w[j - 7] + customBxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10))
  1327.     end
  1328.  
  1329.     local a, b, c, d, e, f, g, h = H[1], H[2], H[3], H[4], H[5], H[6], H[7], H[8]
  1330.     for i = 1, 64 do
  1331.         local s0 = customBxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22))
  1332.         local maj = customBxor(customBand(a, b), customBand(a, c), customBand(b, c))
  1333.         local t2 = s0 + maj
  1334.         local s1 = customBxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25))
  1335.         local ch = customBxor (customBand(e, f), customBand(bnot(e), g))
  1336.         local t1 = h + s1 + ch + k[i] + w[i]
  1337.         h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2
  1338.     end
  1339.  
  1340.     H[1] = customBand(H[1] + a)
  1341.     H[2] = customBand(H[2] + b)
  1342.     H[3] = customBand(H[3] + c)
  1343.     H[4] = customBand(H[4] + d)
  1344.     H[5] = customBand(H[5] + e)
  1345.     H[6] = customBand(H[6] + f)
  1346.     H[7] = customBand(H[7] + g)
  1347.     H[8] = customBand(H[8] + h)
  1348. end
  1349.  
  1350.  
  1351. local function sha256(msg)
  1352.     msg = preproc(msg, #msg)
  1353.     local H = initH256({})
  1354.     for i = 1, #msg, 64 do
  1355.         digestblock(msg, i, H)
  1356.     end
  1357.     return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) ..
  1358.         num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4))
  1359. end
  1360.  
  1361.  
  1362. local protocolName = "Firewolf"
  1363.  
  1364.  
  1365.  
  1366. --    Cryptography
  1367.  
  1368.  
  1369. local Cryptography = {}
  1370. Cryptography.sha = {}
  1371. Cryptography.base64 = {}
  1372. Cryptography.aes = {}
  1373.  
  1374.  
  1375. function Cryptography.bytesFromMessage(msg)
  1376.     local bytes = {}
  1377.  
  1378.     for i = 1, msg:len() do
  1379.         local letter = string.byte(msg:sub(i, i))
  1380.         table.insert(bytes, letter)
  1381.     end
  1382.  
  1383.     return bytes
  1384. end
  1385.  
  1386.  
  1387. function Cryptography.messageFromBytes(bytes)
  1388.     local msg = ""
  1389.  
  1390.     for i = 1, #bytes do
  1391.         local letter = string.char(bytes[i])
  1392.         msg = msg .. letter
  1393.     end
  1394.  
  1395.     return msg
  1396. end
  1397.  
  1398.  
  1399. function Cryptography.bytesFromKey(key)
  1400.     local bytes = {}
  1401.  
  1402.     for i = 1, key:len() / 2 do
  1403.         local group = key:sub((i - 1) * 2 + 1, (i - 1) * 2 + 1)
  1404.         local num = tonumber(group, 16)
  1405.         table.insert(bytes, num)
  1406.     end
  1407.  
  1408.     return bytes
  1409. end
  1410.  
  1411.  
  1412. function Cryptography.sha.sha256(msg)
  1413.     return sha256(msg)
  1414. end
  1415.  
  1416.  
  1417. function Cryptography.aes.encrypt(msg, key)
  1418.     return base64Encode(crypt(msg, key))
  1419. end
  1420.  
  1421.  
  1422. function Cryptography.aes.decrypt(msg, key)
  1423.     return crypt(base64Decode(msg), key)
  1424. end
  1425.  
  1426.  
  1427. function Cryptography.base64.encode(msg)
  1428.     return base64Encode(Cryptography.bytesFromMessage(msg))
  1429. end
  1430.  
  1431.  
  1432. function Cryptography.base64.decode(msg)
  1433.     return Cryptography.messageFromBytes(base64Decode(msg))
  1434. end
  1435.  
  1436.  
  1437. function Cryptography.channel(text)
  1438.     local hashed = Cryptography.sha.sha256(text)
  1439.  
  1440.     local total = 0
  1441.  
  1442.     for i = 1, hashed:len() do
  1443.         total = total + string.byte(hashed:sub(i, i))
  1444.     end
  1445.  
  1446.     return (total % 55530) + 10000
  1447. end
  1448.  
  1449.  
  1450. function Cryptography.sanatize(text)
  1451.     local sanatizeChars = {"%", "(", ")", "[", "]", ".", "+", "-", "*", "?", "^", "$"}
  1452.  
  1453.     for _, char in pairs(sanatizeChars) do
  1454.         text = text:gsub("%"..char, "%%%"..char)
  1455.     end
  1456.     return text
  1457. end
  1458.  
  1459.  
  1460.  
  1461. --  Modem
  1462.  
  1463.  
  1464. local Modem = {}
  1465. Modem.modems = {}
  1466.  
  1467.  
  1468. function Modem.exists()
  1469.     Modem.exists = false
  1470.     for _, side in pairs(rs.getSides()) do
  1471.         if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then
  1472.             Modem.exists = true
  1473.  
  1474.             if not Modem.modems[side] then
  1475.                 Modem.modems[side] = peripheral.wrap(side)
  1476.             end
  1477.         end
  1478.     end
  1479.  
  1480.     return Modem.exists
  1481. end
  1482.  
  1483.  
  1484. function Modem.open(channel)
  1485.     if not Modem.exists then
  1486.         return false
  1487.     end
  1488.  
  1489.     for side, modem in pairs(Modem.modems) do
  1490.         modem.open(channel)
  1491.         rednet.open(side)
  1492.     end
  1493.  
  1494.     return true
  1495. end
  1496.  
  1497.  
  1498. function Modem.close(channel)
  1499.     if not Modem.exists then
  1500.         return false
  1501.     end
  1502.  
  1503.     for side, modem in pairs(Modem.modems) do
  1504.         modem.close(channel)
  1505.     end
  1506.  
  1507.     return true
  1508. end
  1509.  
  1510.  
  1511. function Modem.closeAll()
  1512.     if not Modem.exists then
  1513.         return false
  1514.     end
  1515.  
  1516.     for side, modem in pairs(Modem.modems) do
  1517.         modem.closeAll()
  1518.     end
  1519.  
  1520.     return true
  1521. end
  1522.  
  1523.  
  1524. function Modem.isOpen(channel)
  1525.     if not Modem.exists then
  1526.         return false
  1527.     end
  1528.  
  1529.     local isOpen = false
  1530.     for side, modem in pairs(Modem.modems) do
  1531.         if modem.isOpen(channel) then
  1532.             isOpen = true
  1533.             break
  1534.         end
  1535.     end
  1536.  
  1537.     return isOpen
  1538. end
  1539.  
  1540.  
  1541. function Modem.transmit(channel, msg)
  1542.     if not Modem.exists then
  1543.         return false
  1544.     end
  1545.  
  1546.     if not Modem.isOpen(channel) then
  1547.         Modem.open(channel)
  1548.     end
  1549.  
  1550.     for side, modem in pairs(Modem.modems) do
  1551.         modem.transmit(channel, channel, msg)
  1552.     end
  1553.  
  1554.     return true
  1555. end
  1556.  
  1557.  
  1558.  
  1559. --    Handshake
  1560.  
  1561.  
  1562. local Handshake = {}
  1563.  
  1564. Handshake.prime = 625210769
  1565. Handshake.channel = 54569
  1566. Handshake.base = -1
  1567. Handshake.secret = -1
  1568. Handshake.sharedSecret = -1
  1569. Handshake.packetHeader = "["..protocolName.."-Handshake-Packet-Header]"
  1570. Handshake.packetMatch = "%["..protocolName.."%-Handshake%-Packet%-Header%](.+)"
  1571.  
  1572.  
  1573. function Handshake.exponentWithModulo(base, exponent, modulo)
  1574.     local remainder = base
  1575.  
  1576.     for i = 1, exponent-1 do
  1577.         remainder = remainder * remainder
  1578.         if remainder >= modulo then
  1579.             remainder = remainder % modulo
  1580.         end
  1581.     end
  1582.  
  1583.     return remainder
  1584. end
  1585.  
  1586.  
  1587. function Handshake.clear()
  1588.     Handshake.base = -1
  1589.     Handshake.secret = -1
  1590.     Handshake.sharedSecret = -1
  1591. end
  1592.  
  1593.  
  1594. function Handshake.generateInitiatorData()
  1595.     Handshake.base = math.random(10,99999)
  1596.     Handshake.secret = math.random(10,99999)
  1597.     return {
  1598.         type = "initiate",
  1599.         prime = Handshake.prime,
  1600.         base = Handshake.base,
  1601.         moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime)
  1602.     }
  1603. end
  1604.  
  1605.  
  1606. function Handshake.generateResponseData(initiatorData)
  1607.     local isPrimeANumber = type(initiatorData.prime) == "number"
  1608.     local isPrimeMatching = initiatorData.prime == Handshake.prime
  1609.     local isBaseANumber = type(initiatorData.base) == "number"
  1610.     local isInitiator = initiatorData.type == "initiate"
  1611.     local isModdedSecretANumber = type(initiatorData.moddedSecret) == "number"
  1612.     local areAllNumbersNumbers = isPrimeANumber and isBaseANumber and isModdedSecretANumber
  1613.  
  1614.     if areAllNumbersNumbers and isPrimeMatching then
  1615.         if isInitiator then
  1616.             Handshake.base = initiatorData.base
  1617.             Handshake.secret = math.random(10,99999)
  1618.             Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime)
  1619.             return {
  1620.                 type = "response",
  1621.                 prime = Handshake.prime,
  1622.                 base = Handshake.base,
  1623.                 moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime)
  1624.             }, Handshake.sharedSecret
  1625.         elseif initiatorData.type == "response" and Handshake.base > 0 and Handshake.secret > 0 then
  1626.             Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime)
  1627.             return Handshake.sharedSecret
  1628.         else
  1629.             return false
  1630.         end
  1631.     else
  1632.         return false
  1633.     end
  1634. end
  1635.  
  1636.  
  1637.  
  1638. --    Secure Connection
  1639.  
  1640.  
  1641. local SecureConnection = {}
  1642. SecureConnection.__index = SecureConnection
  1643.  
  1644.  
  1645. SecureConnection.packetHeaderA = "["..protocolName.."-"
  1646. SecureConnection.packetHeaderB = "-SecureConnection-Packet-Header]"
  1647. SecureConnection.packetMatchA = "%["..protocolName.."%-"
  1648. SecureConnection.packetMatchB = "%-SecureConnection%-Packet%-Header%](.+)"
  1649. SecureConnection.connectionTimeout = 0.1
  1650. SecureConnection.successPacketTimeout = 0.1
  1651.  
  1652.  
  1653. function SecureConnection.new(secret, key, identifier, distance, isRednet)
  1654.     local self = setmetatable({}, SecureConnection)
  1655.     self:setup(secret, key, identifier, distance, isRednet)
  1656.     return self
  1657. end
  1658.  
  1659.  
  1660. function SecureConnection:setup(secret, key, identifier, distance, isRednet)
  1661.     local rawSecret
  1662.  
  1663.     if isRednet then
  1664.         self.isRednet = true
  1665.         self.distance = -1
  1666.         self.rednet_id = distance
  1667.         rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) ..
  1668.         "|" .. tostring(key) .. "|rednet"
  1669.     else
  1670.         self.isRednet = false
  1671.         self.distance = distance
  1672.         rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) ..
  1673.         "|" .. tostring(key) .. "|" .. tostring(distance)
  1674.     end
  1675.  
  1676.     self.identifier = identifier
  1677.     self.packetMatch = SecureConnection.packetMatchA .. Cryptography.sanatize(identifier) .. SecureConnection.packetMatchB
  1678.     self.packetHeader = SecureConnection.packetHeaderA .. identifier .. SecureConnection.packetHeaderB
  1679.     self.secret = Cryptography.sha.sha256(rawSecret)
  1680.     self.channel = Cryptography.channel(self.secret)
  1681.  
  1682.     if not self.isRednet then
  1683.         Modem.open(self.channel)
  1684.     end
  1685. end
  1686.  
  1687.  
  1688. function SecureConnection:verifyHeader(msg)
  1689.     if msg:match(self.packetMatch) then
  1690.         return true
  1691.     else
  1692.         return false
  1693.     end
  1694. end
  1695.  
  1696.  
  1697. function SecureConnection:sendMessage(msg, rednetProtocol)
  1698.     local rawEncryptedMsg = Cryptography.aes.encrypt(self.packetHeader .. msg, self.secret)
  1699.     local encryptedMsg = self.packetHeader .. rawEncryptedMsg
  1700.  
  1701.     if self.isRednet then
  1702.         rednet.send(self.rednet_id, encryptedMsg, rednetProtocol)
  1703.         return true
  1704.     else
  1705.         return Modem.transmit(self.channel, encryptedMsg)
  1706.     end
  1707. end
  1708.  
  1709.  
  1710. function SecureConnection:decryptMessage(msg)
  1711.     if self:verifyHeader(msg) then
  1712.         local encrypted = msg:match(self.packetMatch)
  1713.  
  1714.         local unencryptedMsg = nil
  1715.         pcall(function() unencryptedMsg = Cryptography.aes.decrypt(encrypted, self.secret) end)
  1716.         if not unencryptedMsg then
  1717.             return false, "Could not decrypt"
  1718.         end
  1719.  
  1720.         if self:verifyHeader(unencryptedMsg) then
  1721.             return true, unencryptedMsg:match(self.packetMatch)
  1722.         else
  1723.             return false, "Could not verify"
  1724.         end
  1725.     else
  1726.         return false, "Could not stage 1 verify"
  1727.     end
  1728. end
  1729.  
  1730.  
  1731.  
  1732. --    RDNT Protocol
  1733.  
  1734.  
  1735. protocols["rdnt"] = {}
  1736.  
  1737. local header = {}
  1738. header.dnsPacket = "[Firewolf-DNS-Packet]"
  1739. header.dnsHeaderMatch = "^%[Firewolf%-DNS%-Response%](.+)$"
  1740. header.rednetHeader = "[Firewolf-Rednet-Channel-Simulation]"
  1741. header.rednetMatch = "^%[Firewolf%-Rednet%-Channel%-Simulation%](%d+)$"
  1742. header.responseMatchA = "^%[Firewolf%-"
  1743. header.responseMatchB = "%-"
  1744. header.responseMatchC = "%-Handshake%-Response%](.+)$"
  1745. header.requestHeaderA = "[Firewolf-"
  1746. header.requestHeaderB = "-Handshake-Request]"
  1747. header.pageRequestHeaderA = "[Firewolf-"
  1748. header.pageRequestHeaderB = "-Page-Request]"
  1749. header.pageResponseMatchA = "^%[Firewolf%-"
  1750. header.pageResponseMatchB = "%-Page%-Response%]%[HEADER%](.-)%[BODY%](.+)$"
  1751. header.closeHeaderA = "[Firewolf-"
  1752. header.closeHeaderB = "-Connection-Close]"
  1753.  
  1754.  
  1755. protocols["rdnt"]["setup"] = function()
  1756.     if not Modem.exists() then
  1757.         error("No modem found!")
  1758.     end
  1759. end
  1760.  
  1761.  
  1762. protocols["rdnt"]["fetchAllSearchResults"] = function()
  1763.     Modem.open(publicDNSChannel)
  1764.     Modem.open(publicResponseChannel)
  1765.     Modem.transmit(publicDNSChannel, header.dnsPacket)
  1766.     Modem.close(publicDNSChannel)
  1767.  
  1768.     rednet.broadcast(header.dnsPacket, header.rednetHeader .. publicDNSChannel)
  1769.  
  1770.     local uniqueServers = {}
  1771.     local uniqueDomains = {}
  1772.  
  1773.     local timer = os.startTimer(searchResultTimeout)
  1774.  
  1775.     while true do
  1776.         local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1777.         if event == "modem_message" then
  1778.             if channel == publicResponseChannel and message:match(header.dnsHeaderMatch) then
  1779.                 if not uniqueServers[tostring(dist)] then
  1780.                     uniqueServers[tostring(dist)] = true
  1781.                     local domain = message:match(header.dnsHeaderMatch)
  1782.                     if not uniqueDomains[domain] then
  1783.                         if not(domain:find("/") or domain:find(":") or domain:find("%?")) and #domain > 4 then
  1784.                             timer = os.startTimer(searchResultTimeout)
  1785.                             uniqueDomains[message:match(header.dnsHeaderMatch)] = tostring(dist)
  1786.                         end
  1787.                     end
  1788.                 end
  1789.             end
  1790.         elseif event == "rednet_message" and allowUnencryptedConnections then
  1791.             if protocol and tonumber(protocol:match(header.rednetMatch)) == publicResponseChannel and channel:match(header.dnsHeaderMatch) then
  1792.                 if not uniqueServers[tostring(id)] then
  1793.                     uniqueServers[tostring(id)] = true
  1794.                     local domain = channel:match(header.dnsHeaderMatch)
  1795.                     if not uniqueDomains[domain] then
  1796.                         if not(domain:find("/") or domain:find(":") or domain:find("%?")) and #domain > 4 then
  1797.                             timer = os.startTimer(searchResultTimeout)
  1798.                             uniqueDomains[domain] = tostring(id)
  1799.                         end
  1800.                     end
  1801.                 end
  1802.             end
  1803.         elseif event == "timer" and id == timer then
  1804.             local results = {}
  1805.             for k, _ in pairs(uniqueDomains) do
  1806.                 table.insert(results, k)
  1807.             end
  1808.  
  1809.             return results
  1810.         end
  1811.     end
  1812. end
  1813.  
  1814.  
  1815. protocols["rdnt"]["fetchConnectionObject"] = function(url)
  1816.     local serverChannel = Cryptography.channel(url)
  1817.     local requestHeader = header.requestHeaderA .. url .. header.requestHeaderB
  1818.     local responseMatch = header.responseMatchA .. Cryptography.sanatize(url) .. header.responseMatchB
  1819.  
  1820.     local serializedHandshake = textutils.serialize(Handshake.generateInitiatorData())
  1821.  
  1822.     local rednetResults = {}
  1823.     local directResults = {}
  1824.  
  1825.     local disconnectOthers = function(ignoreDirect)
  1826.         for k,v in pairs(rednetResults) do
  1827.             v.close()
  1828.         end
  1829.         for k,v in pairs(directResults) do
  1830.             if k ~= ignoreDirect then
  1831.                 v.close()
  1832.             end
  1833.         end
  1834.     end
  1835.  
  1836.     local timer = os.startTimer(initiationTimeout)
  1837.  
  1838.     Modem.open(serverChannel)
  1839.     Modem.transmit(serverChannel, requestHeader .. serializedHandshake)
  1840.  
  1841.     rednet.broadcast(requestHeader .. serializedHandshake, header.rednetHeader .. serverChannel)
  1842.  
  1843.     -- Extendable to have server selection
  1844.  
  1845.     while true do
  1846.         local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1847.         if event == "modem_message" then
  1848.             local fullMatch = responseMatch .. tostring(dist) .. header.responseMatchC
  1849.             if channel == serverChannel and message:match(fullMatch) and type(textutils.unserialize(message:match(fullMatch))) == "table" then
  1850.                 local key = Handshake.generateResponseData(textutils.unserialize(message:match(fullMatch)))
  1851.                 if key then
  1852.                     local connection = SecureConnection.new(key, url, url, dist)
  1853.                     table.insert(directResults, {
  1854.                         connection = connection,
  1855.                         fetchPage = function(page)
  1856.                             if not connection then
  1857.                                 return nil
  1858.                             end
  1859.  
  1860.                             local fetchTimer = os.startTimer(fetchTimeout)
  1861.  
  1862.                             local pageRequest = header.pageRequestHeaderA .. url .. header.pageRequestHeaderB .. page
  1863.                             local pageResponseMatch = header.pageResponseMatchA .. Cryptography.sanatize(url) .. header.pageResponseMatchB
  1864.  
  1865.                             connection:sendMessage(pageRequest, header.rednetHeader .. connection.channel)
  1866.  
  1867.                             while true do
  1868.                                 local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1869.                                 if event == "modem_message" and channel == connection.channel and connection:verifyHeader(message) then
  1870.                                     local resp, data = connection:decryptMessage(message)
  1871.                                     if not resp then
  1872.                                         -- Decryption error
  1873.                                     elseif data and data ~= page then
  1874.                                         if data:match(pageResponseMatch) then
  1875.                                             local head, body = data:match(pageResponseMatch)
  1876.                                             return body, textutils.unserialize(head)
  1877.                                         end
  1878.                                     end
  1879.                                 elseif event == "timer" and id == fetchTimer then
  1880.                                     return nil
  1881.                                 end
  1882.                             end
  1883.                         end,
  1884.                         close = function()
  1885.                             if connection ~= nil then
  1886.                                 connection:sendMessage(header.closeHeaderA .. url .. header.closeHeaderB, header.rednetHeader..connection.channel)
  1887.                                 Modem.close(connection.channel)
  1888.                                 connection = nil
  1889.                             end
  1890.                         end
  1891.                     })
  1892.  
  1893.                     disconnectOthers(1)
  1894.                     return directResults[1]
  1895.                 end
  1896.             end
  1897.         elseif event == "rednet_message" then
  1898.             local fullMatch = responseMatch .. os.getComputerID() .. header.responseMatchC
  1899.             if protocol and tonumber(protocol:match(header.rednetMatch)) == serverChannel and channel:match(fullMatch) and type(textutils.unserialize(channel:match(fullMatch))) == "table" then
  1900.                 local key = Handshake.generateResponseData(textutils.unserialize(channel:match(fullMatch)))
  1901.                 if key then
  1902.                     local connection = SecureConnection.new(key, url, url, id, true)
  1903.                     table.insert(rednetResults, {
  1904.                         connection = connection,
  1905.                         fetchPage = function(page)
  1906.                             if not connection then
  1907.                                 return nil
  1908.                             end
  1909.  
  1910.                             local fetchTimer = os.startTimer(fetchTimeout)
  1911.  
  1912.                             local pageRequest = header.pageRequestHeaderA .. url .. header.pageRequestHeaderB .. page
  1913.                             local pageResponseMatch = header.pageResponseMatchA .. Cryptography.sanatize(url) .. header.pageResponseMatchB
  1914.  
  1915.                             connection:sendMessage(pageRequest, header.rednetHeader .. connection.channel)
  1916.  
  1917.                             while true do
  1918.                                 local event, id, channel, protocol, message, dist = os.pullEventRaw()
  1919.                                 if event == "rednet_message" and protocol and tonumber(protocol:match(header.rednetMatch)) == connection.channel and connection:verifyHeader(channel) then
  1920.                                     local resp, data = connection:decryptMessage(channel)
  1921.                                     if not resp then
  1922.                                         -- Decryption error
  1923.                                     elseif data and data ~= page then
  1924.                                         if data:match(pageResponseMatch) then
  1925.                                             local head, body = data:match(pageResponseMatch)
  1926.                                             return body, textutils.unserialize(head)
  1927.                                         end
  1928.                                     end
  1929.                                 elseif event == "timer" and id == fetchTimer then
  1930.                                     return nil
  1931.                                 end
  1932.                             end
  1933.                         end,
  1934.                         close = function()
  1935.                             connection:sendMessage(header.closeHeaderA .. url .. header.closeHeaderB, header.rednetHeader..connection.channel)
  1936.                             Modem.close(connection.channel)
  1937.                             connection = nil
  1938.                         end
  1939.                     })
  1940.  
  1941.                     if #rednetResults == 1 then
  1942.                         timer = os.startTimer(0.2)
  1943.                     end
  1944.                 end
  1945.             end
  1946.         elseif event == "timer" and id == timer then
  1947.             -- Return
  1948.             if #directResults > 0 then
  1949.                 disconnectOthers(1)
  1950.                 return directResults[1]
  1951.             elseif #rednetResults > 0 then
  1952.                 local lowestID = math.huge
  1953.                 local lowestResult = nil
  1954.                 for k,v in pairs(rednetResults) do
  1955.                     if v.connection.rednet_id < lowestID then
  1956.                         lowestID = v.connection.rednet_id
  1957.                         lowestResult = v
  1958.                     end
  1959.                 end
  1960.  
  1961.                 for k,v in pairs(rednetResults) do
  1962.                     if v.connection.rednet_id ~= lowestID then
  1963.                         v.close()
  1964.                     end
  1965.                 end
  1966.  
  1967.                 return lowestResult
  1968.             else
  1969.                 return nil
  1970.             end
  1971.         end
  1972.     end
  1973. end
  1974.  
  1975.  
  1976.  
  1977. --    Fetching Raw Data
  1978.  
  1979.  
  1980. local fetchSearchResultsForQuery = function(query)
  1981.     local all = protocols[currentProtocol]["fetchAllSearchResults"]()
  1982.     local results = {}
  1983.     if query and query:len() > 0 then
  1984.         for _, v in pairs(all) do
  1985.             if v:find(query:lower()) then
  1986.                 table.insert(results, v)
  1987.             end
  1988.         end
  1989.     else
  1990.         results = all
  1991.     end
  1992.  
  1993.     table.sort(results)
  1994.     return results
  1995. end
  1996.  
  1997.  
  1998. local getConnectionObjectFromURL = function(url)
  1999.     local domain = url:match("^([^/]+)")
  2000.     return protocols[currentProtocol]["fetchConnectionObject"](domain)
  2001. end
  2002.  
  2003.  
  2004. local determineLanguage = function(header)
  2005.     if type(header) == "table" then
  2006.         if header.language and header.language == "Firewolf Markup" then
  2007.             return "fwml"
  2008.         else
  2009.             return "lua"
  2010.         end
  2011.     else
  2012.         return "lua"
  2013.     end
  2014. end
  2015.  
  2016.  
  2017.  
  2018. --    History
  2019.  
  2020.  
  2021. local appendToHistory = function(url)
  2022.     if history[1] ~= url then
  2023.         table.insert(history, 1, url)
  2024.     end
  2025. end
  2026.  
  2027.  
  2028.  
  2029. --    Fetch Websites
  2030.  
  2031.  
  2032. local loadingAnimation = function()
  2033.     local state = -2
  2034.  
  2035.     term.setTextColor(theme.text)
  2036.     term.setBackgroundColor(theme.accent)
  2037.  
  2038.     term.setCursorPos(w - 5, 1)
  2039.     term.write("[=  ]")
  2040.  
  2041.     local timer = os.startTimer(animationInterval)
  2042.  
  2043.     while true do
  2044.         local event, timerID = os.pullEvent()
  2045.         if event == "timer" and timerID == timer then
  2046.             term.setTextColor(theme.text)
  2047.             term.setBackgroundColor(theme.accent)
  2048.  
  2049.             state = state + 1
  2050.  
  2051.             term.setCursorPos(w - 5, 1)
  2052.             term.write("[   ]")
  2053.             term.setCursorPos(w - 2 - math.abs(state), 1)
  2054.             term.write("=")
  2055.  
  2056.             if state == 2 then
  2057.                 state = -2
  2058.             end
  2059.  
  2060.             timer = os.startTimer(animationInterval)
  2061.         end
  2062.     end
  2063. end
  2064.  
  2065.  
  2066. local normalizeURL = function(url)
  2067.     url = url:lower():gsub(" ", "")
  2068.     if url == "home" or url == "homepage" then
  2069.         url = "firewolf"
  2070.     end
  2071.  
  2072.     return url
  2073. end
  2074.  
  2075.  
  2076. local normalizePage = function(page)
  2077.     if not page then page = "" end
  2078.     page = page:lower()
  2079.     if page == "" then
  2080.         page = "/"
  2081.     end
  2082.     return page
  2083. end
  2084.  
  2085.  
  2086. local determineActionForURL = function(url)
  2087.     if url:len() > 0 and url:gsub("/", ""):len() == 0 then
  2088.         return "none"
  2089.     end
  2090.  
  2091.     if url == "exit" then
  2092.         return "exit"
  2093.     elseif builtInSites["display"][url] then
  2094.         return "internal website"
  2095.     elseif url == "" then
  2096.         local results = fetchSearchResultsForQuery()
  2097.         if #results > 0 then
  2098.             return "search", results
  2099.         else
  2100.             return "none"
  2101.         end
  2102.     else
  2103.         local connection = getConnectionObjectFromURL(url)
  2104.         if connection then
  2105.             return "external website", connection
  2106.         else
  2107.             local results = fetchSearchResultsForQuery(url)
  2108.             if #results > 0 then
  2109.                 return "search", results
  2110.             else
  2111.                 return "none"
  2112.             end
  2113.         end
  2114.     end
  2115. end
  2116.  
  2117.  
  2118. local fetchSearch = function(url, results)
  2119.     return languages["lua"]["runWithoutAntivirus"](builtInSites["search"], results)
  2120. end
  2121.  
  2122.  
  2123. local fetchInternal = function(url)
  2124.     return languages["lua"]["runWithoutAntivirus"](builtInSites["display"][url])
  2125. end
  2126.  
  2127.  
  2128. local fetchError = function(err)
  2129.     return languages["lua"]["runWithoutAntivirus"](builtInSites["error"], err)
  2130. end
  2131.  
  2132.  
  2133. local fetchExternal = function(url, connection)
  2134.     if connection.multipleServers then
  2135.         -- Please forgive me
  2136.         -- GravityScore forced me to do it like this
  2137.         -- I don't mean it, I really don't.
  2138.         connection = connection.servers[1]
  2139.     end
  2140.  
  2141.     local page = normalizePage(url:match("^[^/]+/(.+)"))
  2142.     local contents, head = connection.fetchPage(page)
  2143.     if contents then
  2144.         if type(contents) ~= "string" then
  2145.             return fetchNone()
  2146.         else
  2147.             local language = determineLanguage(head)
  2148.             return languages[language]["run"](contents, page, connection)
  2149.         end
  2150.     else
  2151.         if connection then
  2152.             connection.close()
  2153.             return "retry"
  2154.         end
  2155.         return fetchError("A connection error/timeout has occurred!")
  2156.     end
  2157. end
  2158.  
  2159.  
  2160. local fetchNone = function()
  2161.     return languages["lua"]["runWithoutAntivirus"](builtInSites["noresults"])
  2162. end
  2163.  
  2164.  
  2165. local fetchURL = function(url, inheritConnection)
  2166.     url = normalizeURL(url)
  2167.     currentWebsiteURL = url
  2168.  
  2169.     if inheritConnection then
  2170.         local resp = fetchExternal(url, inheritConnection)
  2171.         if resp ~= "retry" then
  2172.             return resp, false, inheritConnection
  2173.         end
  2174.     end
  2175.  
  2176.     local action, connection = determineActionForURL(url)
  2177.  
  2178.     if action == "search" then
  2179.         return fetchSearch(url, connection), true
  2180.     elseif action == "internal website" then
  2181.         return fetchInternal(url), true
  2182.     elseif action == "external website" then
  2183.         local resp = fetchExternal(url, connection)
  2184.         if resp == "retry" then
  2185.             return fetchError("A connection error/timeout has occurred!"), false, connection
  2186.         else
  2187.             return resp, false, connection
  2188.         end
  2189.     elseif action == "none" then
  2190.         return fetchNone(), true
  2191.     elseif action == "exit" then
  2192.         os.queueEvent("terminate")
  2193.     end
  2194.  
  2195.     return nil
  2196. end
  2197.  
  2198.  
  2199.  
  2200. --    Tabs
  2201.  
  2202.  
  2203. local switchTab = function(index, shouldntResume)
  2204.     if not tabs[index] then
  2205.         return
  2206.     end
  2207.  
  2208.     if tabs[currentTab].win then
  2209.         tabs[currentTab].win.setVisible(false)
  2210.     end
  2211.  
  2212.     currentTab = index
  2213.     isMenubarOpen = tabs[currentTab].isMenubarOpen
  2214.     currentWebsiteURL = tabs[currentTab].url
  2215.  
  2216.     term.redirect(originalTerminal)
  2217.     clear(theme.background, theme.text)
  2218.     drawMenubar()
  2219.  
  2220.     term.redirect(tabs[currentTab].win)
  2221.     term.setCursorPos(1, 1)
  2222.     tabs[currentTab].win.setVisible(true)
  2223.     tabs[currentTab].win.redraw()
  2224.  
  2225.     if not shouldntResume then
  2226.         coroutine.resume(tabs[currentTab].thread)
  2227.     end
  2228. end
  2229.  
  2230.  
  2231. local closeCurrentTab = function()
  2232.     if #tabs <= 0 then
  2233.         return
  2234.     end
  2235.  
  2236.     table.remove(tabs, currentTab)
  2237.  
  2238.     currentTab = math.max(currentTab - 1, 1)
  2239.     switchTab(currentTab, true)
  2240. end
  2241.  
  2242.  
  2243. local loadTab = function(index, url, givenFunc)
  2244.     url = normalizeURL(url)
  2245.  
  2246.     local func = nil
  2247.     local isOpen = true
  2248.     local currentConnection = false
  2249.  
  2250.     isMenubarOpen = true
  2251.     currentWebsiteURL = url
  2252.     drawMenubar()
  2253.  
  2254.     if tabs[index] and tabs[index].connection and tabs[index].url then
  2255.         if url:match("^([^/]+)") == tabs[index].url:match("^([^/]+)") then
  2256.             currentConnection = tabs[index].connection
  2257.         else
  2258.             tabs[index].connection.close()
  2259.             tabs[index].connection = nil
  2260.         end
  2261.     end
  2262.  
  2263.     if givenFunc then
  2264.         func = givenFunc
  2265.     else
  2266.         parallel.waitForAny(function()
  2267.             func, isOpen, connection = fetchURL(url, currentConnection)
  2268.         end, function()
  2269.             while true do
  2270.                 local event, key = os.pullEvent()
  2271.                 if event == "key" and (key == 29 or key == 157) then
  2272.                     break
  2273.                 end
  2274.             end
  2275.         end, loadingAnimation)
  2276.     end
  2277.  
  2278.     if func then
  2279.         appendToHistory(url)
  2280.  
  2281.         tabs[index] = {}
  2282.         tabs[index].url = url
  2283.         tabs[index].connection = connection
  2284.         tabs[index].win = window.create(originalTerminal, 1, 1, w, h, false)
  2285.  
  2286.         tabs[index].thread = coroutine.create(func)
  2287.         tabs[index].isMenubarOpen = isOpen
  2288.         tabs[index].isMenubarPermanent = isOpen
  2289.  
  2290.         tabs[index].ox = 1
  2291.         tabs[index].oy = 1
  2292.  
  2293.         term.redirect(tabs[index].win)
  2294.         clear(theme.background, theme.text)
  2295.  
  2296.         switchTab(index)
  2297.     end
  2298. end
  2299.  
  2300.  
  2301.  
  2302. --    Website Environments
  2303.  
  2304.  
  2305. local getWhitelistedEnvironment = function()
  2306.     local env = {}
  2307.  
  2308.     local function copy(source, destination, key)
  2309.         destination[key] = {}
  2310.         for k, v in pairs(source) do
  2311.             destination[key][k] = v
  2312.         end
  2313.     end
  2314.  
  2315.     copy(bit, env, "bit")
  2316.     copy(colors, env, "colors")
  2317.     copy(colours, env, "colours")
  2318.     copy(coroutine, env, "coroutine")
  2319.  
  2320.     copy(disk, env, "disk")
  2321.     env["disk"]["setLabel"] = nil
  2322.     env["disk"]["eject"] = nil
  2323.  
  2324.     copy(gps, env, "gps")
  2325.     copy(help, env, "help")
  2326.     copy(keys, env, "keys")
  2327.     copy(math, env, "math")
  2328.  
  2329.     copy(os, env, "os")
  2330.     env["os"]["run"] = nil
  2331.     env["os"]["shutdown"] = nil
  2332.     env["os"]["reboot"] = nil
  2333.     env["os"]["setComputerLabel"] = nil
  2334.     env["os"]["queueEvent"] = nil
  2335.     env["os"]["pullEvent"] = function(filter)
  2336.         while true do
  2337.             local event = {os.pullEvent(filter)}
  2338.             if not filter then
  2339.                 return unpack(event)
  2340.             elseif filter and event[1] == filter then
  2341.                 return unpack(event)
  2342.             end
  2343.         end
  2344.     end
  2345.     env["os"]["pullEventRaw"] = env["os"]["pullEvent"]
  2346.  
  2347.     copy(paintutils, env, "paintutils")
  2348.     copy(parallel, env, "parallel")
  2349.     copy(peripheral, env, "peripheral")
  2350.     copy(rednet, env, "rednet")
  2351.     copy(redstone, env, "redstone")
  2352.     copy(redstone, env, "rs")
  2353.  
  2354.     copy(shell, env, "shell")
  2355.     env["shell"]["run"] = nil
  2356.     env["shell"]["exit"] = nil
  2357.     env["shell"]["setDir"] = nil
  2358.     env["shell"]["setAlias"] = nil
  2359.     env["shell"]["clearAlias"] = nil
  2360.     env["shell"]["setPath"] = nil
  2361.  
  2362.     copy(string, env, "string")
  2363.     copy(table, env, "table")
  2364.  
  2365.     copy(term, env, "term")
  2366.     env["term"]["redirect"] = nil
  2367.     env["term"]["restore"] = nil
  2368.  
  2369.     copy(textutils, env, "textutils")
  2370.     copy(vector, env, "vector")
  2371.  
  2372.     if turtle then
  2373.         copy(turtle, env, "turtle")
  2374.     end
  2375.  
  2376.     if http then
  2377.         copy(http, env, "http")
  2378.     end
  2379.  
  2380.     env["assert"] = assert
  2381.     env["printError"] = printError
  2382.     env["tonumber"] = tonumber
  2383.     env["tostring"] = tostring
  2384.     env["type"] = type
  2385.     env["next"] = next
  2386.     env["unpack"] = unpack
  2387.     env["pcall"] = pcall
  2388.     env["xpcall"] = xpcall
  2389.     env["sleep"] = sleep
  2390.     env["pairs"] = pairs
  2391.     env["ipairs"] = ipairs
  2392.     env["read"] = read
  2393.     env["write"] = write
  2394.     env["select"] = select
  2395.     env["print"] = print
  2396.     env["setmetatable"] = setmetatable
  2397.     env["getmetatable"] = getmetatable
  2398.  
  2399.     env["_G"] = env
  2400.  
  2401.     return env
  2402. end
  2403.  
  2404.  
  2405. local overrideEnvironment = function(env)
  2406.     local localTerm = {}
  2407.     for k, v in pairs(term) do
  2408.         localTerm[k] = v
  2409.     end
  2410.  
  2411.     env["term"]["clear"] = function()
  2412.         localTerm.clear()
  2413.         drawMenubar()
  2414.     end
  2415.  
  2416.     env["term"]["scroll"] = function(n)
  2417.         localTerm.scroll(n)
  2418.         drawMenubar()
  2419.     end
  2420.  
  2421.     env["shell"]["getRunningProgram"] = function()
  2422.         return currentWebsiteURL
  2423.     end
  2424. end
  2425.  
  2426. local urlEncode = function(url)
  2427.     local result = url
  2428.  
  2429.     result = result:gsub("%%", "%%a")
  2430.     result = result:gsub(":", "%%c")
  2431.     result = result:gsub("/", "%%s")
  2432.     result = result:gsub("\n", "%%n")
  2433.     result = result:gsub(" ", "%%w")
  2434.     result = result:gsub("&", "%%m")
  2435.     result = result:gsub("%?", "%%q")
  2436.     result = result:gsub("=", "%%e")
  2437.     result = result:gsub("%.", "%%d")
  2438.  
  2439.     return result
  2440. end
  2441.  
  2442. local urlDecode = function(url)
  2443.     local result = url
  2444.  
  2445.     result = result:gsub("%%c", ":")
  2446.     result = result:gsub("%%s", "/")
  2447.     result = result:gsub("%%n", "\n")
  2448.     result = result:gsub("%%w", " ")
  2449.     result = result:gsub("%%&", "&")
  2450.     result = result:gsub("%%q", "%?")
  2451.     result = result:gsub("%%e", "=")
  2452.     result = result:gsub("%%d", "%.")
  2453.     result = result:gsub("%%m", "%%")
  2454.  
  2455.     return result
  2456. end
  2457.  
  2458. local applyAPIFunctions = function(env, connection)
  2459.     env["firewolf"] = {}
  2460.     env["firewolf"]["version"] = version
  2461.     env["firewolf"]["domain"] = currentWebsiteURL:match("^[^/]+")
  2462.  
  2463.     env["firewolf"]["redirect"] = function(url)
  2464.         if type(url) ~= "string" then
  2465.             return error("string (url) expected, got " .. type(url))
  2466.         end
  2467.  
  2468.         os.queueEvent(redirectEvent, url)
  2469.         coroutine.yield()
  2470.     end
  2471.  
  2472.     env["firewolf"]["download"] = function(page)
  2473.         if type(page) ~= "string" then
  2474.             return error("string (page) expected")
  2475.         end
  2476.         local bannedNames = {"ls", "dir", "delete", "copy", "move", "list", "rm", "cp", "mv", "clear", "cd", "lua"}
  2477.  
  2478.         local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2479.         if startSearch == 1 then
  2480.             if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2481.                 page = page:sub(endSearch + 2, -1)
  2482.             else
  2483.                 page = page:sub(endSearch + 1, -1)
  2484.             end
  2485.         end
  2486.  
  2487.         local filename = page:match("([^/]+)$")
  2488.         if not filename then
  2489.             return false, "Cannot download index"
  2490.         end
  2491.  
  2492.         for k, v in pairs(bannedNames) do
  2493.             if filename == v then
  2494.                 return false, "Filename prohibited!"
  2495.             end
  2496.         end
  2497.  
  2498.         if not fs.exists(downloadsLocation) then
  2499.             fs.makeDir(downloadsLocation)
  2500.         elseif not fs.isDir(downloadsLocation) then
  2501.             return false, "Downloads disabled!"
  2502.         end
  2503.  
  2504.         contents = connection.fetchPage(normalizePage(page))
  2505.         if type(contents) ~= "string" then
  2506.             return false, "Download error!"
  2507.         else
  2508.             local f = io.open(downloadsLocation .. "/" .. filename, "w")
  2509.             f:write(contents)
  2510.             f:close()
  2511.             return true, downloadsLocation .. "/" .. filename
  2512.         end
  2513.     end
  2514.  
  2515.     env["firewolf"]["encode"] = function(vars)
  2516.         if type(vars) ~= "table" then
  2517.             return error("table (vars) expected, got " .. type(vars))
  2518.         end
  2519.  
  2520.         local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2521.         if startSearch == 1 then
  2522.             if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2523.                 page = page:sub(endSearch + 2, -1)
  2524.             else
  2525.                 page = page:sub(endSearch + 1, -1)
  2526.             end
  2527.         end
  2528.  
  2529.         local construct = "?"
  2530.         for k,v in pairs(vars) do
  2531.             construct = construct .. urlEncode(tostring(k)) .. "=" .. urlEncode(tostring(v)) .. "&"
  2532.         end
  2533.         -- Get rid of that last ampersand
  2534.         construct = construct:sub(1, -2)
  2535.  
  2536.         return construct
  2537.     end
  2538.  
  2539.     env["firewolf"]["query"] = function(page, vars)
  2540.         if type(page) ~= "string" then
  2541.             return error("string (page) expected, got " .. type(page))
  2542.         end
  2543.         if vars and type(vars) ~= "table" then
  2544.             return error("table (vars) expected, got " .. type(vars))
  2545.         end
  2546.  
  2547.         local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2548.         if startSearch == 1 then
  2549.             if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2550.                 page = page:sub(endSearch + 2, -1)
  2551.             else
  2552.                 page = page:sub(endSearch + 1, -1)
  2553.             end
  2554.         end
  2555.  
  2556.         local construct = page .. "?"
  2557.         if vars then
  2558.             for k,v in pairs(vars) do
  2559.                 construct = construct .. urlEncode(tostring(k)) .. "=" .. urlEncode(tostring(v)) .. "&"
  2560.             end
  2561.         end
  2562.         -- Get rid of that last ampersand
  2563.         construct = construct:sub(1, -2)
  2564.  
  2565.         contents = connection.fetchPage(normalizePage(construct))
  2566.         if type(contents) == "string" then
  2567.             return contents
  2568.         else
  2569.             return false
  2570.         end
  2571.     end
  2572.  
  2573.     env["firewolf"]["loadImage"] = function(page)
  2574.         if type(page) ~= "string" then
  2575.             return error("string (page) expected, got " .. type(page))
  2576.         end
  2577.  
  2578.         local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+"))
  2579.         if startSearch == 1 then
  2580.             if page:sub(endSearch + 1, endSearch + 1) == "/" then
  2581.                 page = page:sub(endSearch + 2, -1)
  2582.             else
  2583.                 page = page:sub(endSearch + 1, -1)
  2584.             end
  2585.         end
  2586.  
  2587.         local filename = page:match("([^/]+)$")
  2588.         if not filename then
  2589.             return false, "Cannot load index as an image!"
  2590.         end
  2591.  
  2592.         contents = connection.fetchPage(normalizePage(page))
  2593.         if type(contents) ~= "string" then
  2594.             return false, "Download error!"
  2595.         else
  2596.             local colorLookup = {}
  2597.             for n = 1, 16 do
  2598.                 colorLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1)
  2599.             end
  2600.  
  2601.             local image = {}
  2602.             for line in contents:gmatch("[^\n]+") do
  2603.                 local lines = {}
  2604.                 for x = 1, line:len() do
  2605.                     lines[x] = colorLookup[string.byte(line, x, x)] or 0
  2606.                 end
  2607.                 table.insert(image, lines)
  2608.             end
  2609.  
  2610.             return image
  2611.         end
  2612.     end
  2613.  
  2614.     env["center"] = center
  2615.     env["fill"] = fill
  2616. end
  2617.  
  2618.  
  2619. local getWebsiteEnvironment = function(antivirus, connection)
  2620.     local env = {}
  2621.  
  2622.     if antivirus then
  2623.         env = getWhitelistedEnvironment()
  2624.         overrideEnvironment(env)
  2625.     else
  2626.         setmetatable(env, {__index = _G})
  2627.     end
  2628.  
  2629.     applyAPIFunctions(env, connection)
  2630.  
  2631.     return env
  2632. end
  2633.  
  2634.  
  2635.  
  2636. --    FWML Execution
  2637.  
  2638.  
  2639. local render = {}
  2640.  
  2641. render["functions"] = {}
  2642. render["functions"]["public"] = {}
  2643. render["alignations"] = {}
  2644.  
  2645. render["variables"] = {
  2646.     scroll,
  2647.     maxScroll,
  2648.     align,
  2649.     linkData = {},
  2650.     blockLength,
  2651.     link,
  2652.     linkStart,
  2653.     markers,
  2654.     currentOffset,
  2655. }
  2656.  
  2657.  
  2658. local function getLine(loc, data)
  2659.     local _, changes = data:sub(1, loc):gsub("\n", "")
  2660.     if not changes then
  2661.         return 1
  2662.     else
  2663.         return changes + 1
  2664.     end
  2665. end
  2666.  
  2667.  
  2668. local function parseData(data)
  2669.     local commands = {}
  2670.     local searchPos = 1
  2671.  
  2672.     while #data > 0 do
  2673.         local sCmd, eCmd = data:find("%[[^%]]+%]", searchPos)
  2674.         if sCmd then
  2675.             sCmd = sCmd + 1
  2676.             eCmd = eCmd - 1
  2677.  
  2678.             if (sCmd > 2) then
  2679.                 if data:sub(sCmd - 2, sCmd - 2) == "\\" then
  2680.                     local t = data:sub(searchPos, sCmd - 1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2681.                     if #t > 0 then
  2682.                         if #commands > 0 and type(commands[#commands][1]) == "string" then
  2683.                             commands[#commands][1] = commands[#commands][1] .. t
  2684.                         else
  2685.                             table.insert(commands, {t})
  2686.                         end
  2687.                     end
  2688.                     searchPos = sCmd
  2689.                 else
  2690.                     local t = data:sub(searchPos, sCmd - 2):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2691.                     if #t > 0 then
  2692.                         if #commands > 0 and type(commands[#commands][1]) == "string" then
  2693.                             commands[#commands][1] = commands[#commands][1] .. t
  2694.                         else
  2695.                             table.insert(commands, {t})
  2696.                         end
  2697.                     end
  2698.  
  2699.                     t = data:sub(sCmd, eCmd):gsub("\n", "")
  2700.                     table.insert(commands, {getLine(sCmd, data), t})
  2701.                     searchPos = eCmd + 2
  2702.                 end
  2703.             else
  2704.                 local t = data:sub(sCmd, eCmd):gsub("\n", "")
  2705.                 table.insert(commands, {getLine(sCmd, data), t})
  2706.                 searchPos = eCmd + 2
  2707.             end
  2708.         else
  2709.             local t = data:sub(searchPos, -1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]")
  2710.             if #t > 0 then
  2711.                 if #commands > 0 and type(commands[#commands][1]) == "string" then
  2712.                     commands[#commands][1] = commands[#commands][1] .. t
  2713.                 else
  2714.                     table.insert(commands, {t})
  2715.                 end
  2716.             end
  2717.  
  2718.             break
  2719.         end
  2720.     end
  2721.  
  2722.     return commands
  2723. end
  2724.  
  2725.  
  2726. local function proccessData(commands)
  2727.     searchIndex = 0
  2728.  
  2729.     while searchIndex < #commands do
  2730.         searchIndex = searchIndex + 1
  2731.  
  2732.         local length = 0
  2733.         local origin = searchIndex
  2734.  
  2735.         if type(commands[searchIndex][1]) == "string" then
  2736.             length = length + #commands[searchIndex][1]
  2737.             local endIndex = origin
  2738.             for i = origin + 1, #commands do
  2739.                 if commands[i][2] then
  2740.                     local command = commands[i][2]:match("^(%w+)%s-")
  2741.                     if not (command == "c" or command == "color" or command == "bg"
  2742.                             or command == "background" or command == "newlink" or command == "endlink") then
  2743.                         endIndex = i
  2744.                         break
  2745.                     end
  2746.                 elseif commands[i][2] then
  2747.  
  2748.                 else
  2749.                     length = length + #commands[i][1]
  2750.                 end
  2751.                 if i == #commands then
  2752.                     endIndex = i
  2753.                 end
  2754.             end
  2755.  
  2756.             commands[origin][2] = length
  2757.             searchIndex = endIndex
  2758.             length = 0
  2759.         end
  2760.     end
  2761.  
  2762.     return commands
  2763. end
  2764.  
  2765.  
  2766. local function parse(original)
  2767.     return proccessData(parseData(original))
  2768. end
  2769.  
  2770.  
  2771. render["functions"]["display"] = function(text, length, offset, center)
  2772.     if not offset then
  2773.         offset = 0
  2774.     end
  2775.  
  2776.     return render.variables.align(text, length, w, offset, center);
  2777. end
  2778.  
  2779.  
  2780. render["functions"]["displayText"] = function(source)
  2781.     if source[2] then
  2782.         render.variables.blockLength = source[2]
  2783.         if render.variables.link and not render.variables.linkStart then
  2784.             render.variables.linkStart = render.functions.display(
  2785.                 source[1], render.variables.blockLength, render.variables.currentOffset, w / 2)
  2786.         else
  2787.             render.functions.display(source[1], render.variables.blockLength, render.variables.currentOffset, w / 2)
  2788.         end
  2789.     else
  2790.         if render.variables.link and not render.variables.linkStart then
  2791.             render.variables.linkStart = render.functions.display(source[1], nil, render.variables.currentOffset, w / 2)
  2792.         else
  2793.             render.functions.display(source[1], nil, render.variables.currentOffset, w / 2)
  2794.         end
  2795.     end
  2796. end
  2797.  
  2798.  
  2799. render["functions"]["public"]["br"] = function(source)
  2800.     if render.variables.link then
  2801.         return "Cannot insert new line within a link on line " .. source[1]
  2802.     end
  2803.  
  2804.     render.variables.scroll = render.variables.scroll + 1
  2805.     render.variables.maxScroll = math.max(render.variables.scroll, render.variables.maxScroll)
  2806. end
  2807.  
  2808.  
  2809. render["functions"]["public"]["c "] = function(source)
  2810.     local sColor = source[2]:match("^%w+%s+(.+)$") or ""
  2811.     if colors[sColor] then
  2812.         term.setTextColor(colors[sColor])
  2813.     else
  2814.         return "Invalid color: \"" .. sColor .. "\" on line " .. source[1]
  2815.     end
  2816. end
  2817.  
  2818.  
  2819. render["functions"]["public"]["color "] = render["functions"]["public"]["c "]
  2820.  
  2821.  
  2822. render["functions"]["public"]["bg "] = function(source)
  2823.     local sColor = source[2]:match("^%w+%s+(.+)$") or ""
  2824.     if colors[sColor] then
  2825.         term.setBackgroundColor(colors[sColor])
  2826.     else
  2827.         return "Invalid color: \"" .. sColor .. "\" on line " .. source[1]
  2828.     end
  2829. end
  2830.  
  2831.  
  2832. render["functions"]["public"]["background "] = render["functions"]["public"]["bg "]
  2833.  
  2834.  
  2835. render["functions"]["public"]["newlink "] = function(source)
  2836.     if render.variables.link then
  2837.         return "Cannot nest links on line " .. source[1]
  2838.     end
  2839.  
  2840.     render.variables.link = source[2]:match("^%w+%s+(.+)$") or ""
  2841.     render.variables.linkStart = false
  2842. end
  2843.  
  2844.  
  2845. render["functions"]["public"]["endlink"] = function(source)
  2846.     if not render.variables.link then
  2847.         return "Cannot end a link without a link on line " .. source[1]
  2848.     end
  2849.  
  2850.     local linkEnd = term.getCursorPos()-1
  2851.     table.insert(render.variables.linkData, {render.variables.linkStart,
  2852.         linkEnd, render.variables.scroll, render.variables.link})
  2853.     render.variables.link = false
  2854.     render.variables.linkStart = false
  2855. end
  2856.  
  2857.  
  2858. render["functions"]["public"]["offset "] = function(source)
  2859.     local offset = tonumber((source[2]:match("^%w+%s+(.+)$") or ""))
  2860.     if offset then
  2861.         render.variables.currentOffset = offset
  2862.     else
  2863.         return "Invalid offset value: \"" .. (source[2]:match("^%w+%s+(.+)$") or "") .. "\" on line " .. source[1]
  2864.     end
  2865. end
  2866.  
  2867.  
  2868. render["functions"]["public"]["marker "] = function(source)
  2869.     render.variables.markers[(source[2]:match("^%w+%s+(.+)$") or "")] = render.variables.scroll
  2870. end
  2871.  
  2872.  
  2873. render["functions"]["public"]["goto "] = function(source)
  2874.     local location = source[2]:match("%w+%s+(.+)$")
  2875.     if render.variables.markers[location] then
  2876.         render.variables.scroll = render.variables.markers[location]
  2877.     else
  2878.         return "No such location: \"" .. (source[2]:match("%w+%s+(.+)$") or "") .. "\" on line " .. source[1]
  2879.     end
  2880. end
  2881.  
  2882.  
  2883. render["functions"]["public"]["box "] = function(source)
  2884.     local sColor, align, height, width, offset, url = source[2]:match("^box (%a+) (%a+) (%-?%d+) (%-?%d+) (%-?%d+) ?([^ ]*)")
  2885.     if not sColor then
  2886.         return "Invalid box syntax on line " .. source[1]
  2887.     end
  2888.  
  2889.     local x, y = term.getCursorPos()
  2890.     local startX
  2891.  
  2892.     if align == "center" or align == "centre" then
  2893.         startX = math.ceil((w / 2) - width / 2) + offset
  2894.     elseif align == "left" then
  2895.         startX = 1 + offset
  2896.     elseif align == "right" then
  2897.         startX = (w - width + 1) + offset
  2898.     else
  2899.         return "Invalid align option for box on line " .. source[1]
  2900.     end
  2901.  
  2902.     if not colors[sColor] then
  2903.         return "Invalid color: \"" .. sColor .. "\" for box on line " .. source[1]
  2904.     end
  2905.  
  2906.     term.setBackgroundColor(colors[sColor])
  2907.     for i = 0, height - 1 do
  2908.         term.setCursorPos(startX, render.variables.scroll + i)
  2909.         term.write(string.rep(" ", width))
  2910.         if url:len() > 3 then
  2911.             table.insert(render.variables.linkData, {startX, startX + width - 1, render.variables.scroll + i, url})
  2912.         end
  2913.     end
  2914.  
  2915.     render.variables.maxScroll = math.max(render.variables.scroll + height - 1, render.variables.maxScroll)
  2916.     term.setCursorPos(x, y)
  2917. end
  2918.  
  2919.  
  2920. render["alignations"]["left"] = function(text, length, _, offset)
  2921.     local x, y = term.getCursorPos()
  2922.     if length then
  2923.         term.setCursorPos(1 + offset, render.variables.scroll)
  2924.         term.write(text)
  2925.         return 1 + offset
  2926.     else
  2927.         term.setCursorPos(x, render.variables.scroll)
  2928.         term.write(text)
  2929.         return x
  2930.     end
  2931. end
  2932.  
  2933.  
  2934. render["alignations"]["right"] = function(text, length, width, offset)
  2935.     local x, y = term.getCursorPos()
  2936.     if length then
  2937.         term.setCursorPos((width - length + 1) + offset, render.variables.scroll)
  2938.         term.write(text)
  2939.         return (width - length + 1) + offset
  2940.     else
  2941.         term.setCursorPos(x, render.variables.scroll)
  2942.         term.write(text)
  2943.         return x
  2944.     end
  2945. end
  2946.  
  2947.  
  2948. render["alignations"]["center"] = function(text, length, _, offset, center)
  2949.     local x, y = term.getCursorPos()
  2950.     if length then
  2951.         term.setCursorPos(math.ceil(center - length / 2) + offset, render.variables.scroll)
  2952.         term.write(text)
  2953.         return math.ceil(center - length / 2) + offset
  2954.     else
  2955.         term.setCursorPos(x, render.variables.scroll)
  2956.         term.write(text)
  2957.         return x
  2958.     end
  2959. end
  2960.  
  2961.  
  2962. render["render"] = function(data, startScroll)
  2963.     if startScroll == nil then
  2964.         render.variables.startScroll = 0
  2965.     else
  2966.         render.variables.startScroll = startScroll
  2967.     end
  2968.  
  2969.     render.variables.scroll = startScroll + 1
  2970.     render.variables.maxScroll = render.variables.scroll
  2971.  
  2972.     render.variables.linkData = {}
  2973.  
  2974.     render.variables.align = render.alignations.left
  2975.  
  2976.     render.variables.blockLength = 0
  2977.     render.variables.link = false
  2978.     render.variables.linkStart = false
  2979.     render.variables.markers = {}
  2980.     render.variables.currentOffset = 0
  2981.  
  2982.     for k, v in pairs(data) do
  2983.         if type(v[2]) ~= "string" then
  2984.             render.functions.displayText(v)
  2985.         elseif v[2] == "<" or v[2] == "left" then
  2986.             render.variables.align = render.alignations.left
  2987.         elseif v[2] == ">" or v[2] == "right" then
  2988.             render.variables.align = render.alignations.right
  2989.         elseif v[2] == "=" or v[2] == "center" then
  2990.             render.variables.align = render.alignations.center
  2991.         else
  2992.             local existentFunction = false
  2993.  
  2994.             for name, func in pairs(render.functions.public) do
  2995.                 if v[2]:find(name) == 1 then
  2996.                     existentFunction = true
  2997.                     local ret = func(v)
  2998.                     if ret then
  2999.                         return ret
  3000.                     end
  3001.                 end
  3002.             end
  3003.  
  3004.             if not existentFunction then
  3005.                 return "Non-existent tag: \"" .. v[2] .. "\" on line " .. v[1]
  3006.             end
  3007.         end
  3008.     end
  3009.  
  3010.     return render.variables.linkData, render.variables.maxScroll - render.variables.startScroll
  3011. end
  3012.  
  3013.  
  3014.  
  3015. --    Lua Execution
  3016.  
  3017.  
  3018. languages["lua"] = {}
  3019. languages["fwml"] = {}
  3020.  
  3021.  
  3022. languages["lua"]["runWithErrorCatching"] = function(func, ...)
  3023.     local _, err = pcall(func, ...)
  3024.     if err then
  3025.         os.queueEvent(websiteErrorEvent, err)
  3026.     end
  3027. end
  3028.  
  3029.  
  3030. languages["lua"]["runWithoutAntivirus"] = function(func, ...)
  3031.     local args = {...}
  3032.     local env = getWebsiteEnvironment(false)
  3033.     setfenv(func, env)
  3034.     return function()
  3035.         languages["lua"]["runWithErrorCatching"](func, unpack(args))
  3036.     end
  3037. end
  3038.  
  3039.  
  3040. languages["lua"]["run"] = function(contents, page, connection, ...)
  3041.     local func, err = loadstring("sleep(0) " .. contents, page)
  3042.     if err then
  3043.         return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], err)
  3044.     else
  3045.         local args = {...}
  3046.         local env = getWebsiteEnvironment(true, connection)
  3047.         setfenv(func, env)
  3048.         return function()
  3049.             languages["lua"]["runWithErrorCatching"](func, unpack(args))
  3050.         end
  3051.     end
  3052. end
  3053.  
  3054.  
  3055. languages["fwml"]["run"] = function(contents, page, connection, ...)
  3056.     local err, data = pcall(parse, contents)
  3057.     if not err then
  3058.         return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], data)
  3059.     end
  3060.  
  3061.     return function()
  3062.         local currentScroll = 0
  3063.         local err, links, pageHeight = pcall(render.render, data, currentScroll)
  3064.         if type(links) == "string" or not err then
  3065.             term.clear()
  3066.             os.queueEvent(websiteErrorEvent, links)
  3067.         else
  3068.             while true do
  3069.                 local e, scroll, x, y = os.pullEvent()
  3070.                 if e == "mouse_click" then
  3071.                     for k, v in pairs(links) do
  3072.                         if x >= math.min(v[1], v[2]) and x <= math.max(v[1], v[2]) and y == v[3] then
  3073.                             os.queueEvent(redirectEvent, v[4])
  3074.                             coroutine.yield()
  3075.                         end
  3076.                     end
  3077.                 elseif e == "mouse_scroll" then
  3078.                     if currentScroll - scroll - h >= -pageHeight and currentScroll - scroll <= 0 then
  3079.                         currentScroll = currentScroll - scroll
  3080.                         clear(theme.background, theme.text)
  3081.                         links = render.render(data, currentScroll)
  3082.                     end
  3083.                 elseif e == "key" and scroll == keys.up or scroll == keys.down then
  3084.                     local scrollAmount
  3085.  
  3086.                     if scroll == keys.up then
  3087.                         scrollAmount = 1
  3088.                     elseif scroll == keys.down then
  3089.                         scrollAmount = -1
  3090.                     end
  3091.  
  3092.                     local scrollLessHeight = currentScroll + scrollAmount - h >= -pageHeight
  3093.                     local scrollZero = currentScroll + scrollAmount <= 0
  3094.                     if scrollLessHeight and scrollZero then
  3095.                         currentScroll = currentScroll + scrollAmount
  3096.                         clear(theme.background, theme.text)
  3097.                         links = render.render(data, currentScroll)
  3098.                     end
  3099.                 end
  3100.             end
  3101.         end
  3102.     end
  3103. end
  3104.  
  3105.  
  3106.  
  3107. --    Query Bar
  3108.  
  3109.  
  3110. local readNewWebsiteURL = function()
  3111.     local onEvent = function(text, event, key, x, y)
  3112.         if event == "mouse_click" then
  3113.             if y == 2 then
  3114.                 local index = determineClickedTab(x, y)
  3115.                 if index == "new" and #tabs < maxTabs then
  3116.                     loadTab(#tabs + 1, "firewolf")
  3117.                 elseif index == "close" then
  3118.                     closeCurrentTab()
  3119.                 elseif index then
  3120.                     switchTab(index)
  3121.                 end
  3122.  
  3123.                 return {["nullifyText"] = true, ["exit"] = true}
  3124.             elseif y > 2 then
  3125.                 return {["nullifyText"] = true, ["exit"] = true}
  3126.             end
  3127.         elseif event == "key" then
  3128.             if key == 29 or key == 157 then
  3129.                 return {["nullifyText"] = true, ["exit"] = true}
  3130.             end
  3131.         end
  3132.     end
  3133.  
  3134.     isMenubarOpen = true
  3135.     drawMenubar()
  3136.     term.setCursorPos(2, 1)
  3137.     term.setTextColor(theme.text)
  3138.     term.setBackgroundColor(theme.accent)
  3139.     term.clearLine()
  3140.     term.write(currentProtocol .. "://")
  3141.  
  3142.     local website = modifiedRead({
  3143.         ["onEvent"] = onEvent,
  3144.         ["displayLength"] = w - 9,
  3145.         ["history"] = history,
  3146.     })
  3147.  
  3148.     if not website then
  3149.         if not tabs[currentTab].isMenubarPermanent then
  3150.             isMenubarOpen = false
  3151.             menubarWindow.setVisible(false)
  3152.         else
  3153.             isMenubarOpen = true
  3154.             menubarWindow.setVisible(true)
  3155.         end
  3156.  
  3157.         term.redirect(tabs[currentTab].win)
  3158.         tabs[currentTab].win.setVisible(true)
  3159.         tabs[currentTab].win.redraw()
  3160.  
  3161.         return
  3162.     elseif website == "exit" then
  3163.         error()
  3164.     end
  3165.  
  3166.     loadTab(currentTab, website)
  3167. end
  3168.  
  3169.  
  3170.  
  3171. --    Event Management
  3172.  
  3173.  
  3174. local handleKeyDown = function(event)
  3175.     if event[2] == 29 or event[2] == 157 then
  3176.         readNewWebsiteURL()
  3177.         return true
  3178.     end
  3179.  
  3180.     return false
  3181. end
  3182.  
  3183.  
  3184. local handleMouseDown = function(event)
  3185.     if isMenubarOpen then
  3186.         if event[4] == 1 then
  3187.             readNewWebsiteURL()
  3188.             return true
  3189.         elseif event[4] == 2 then
  3190.             local index = determineClickedTab(event[3], event[4])
  3191.             if index == "new" and #tabs < maxTabs then
  3192.                 loadTab(#tabs + 1, "firewolf")
  3193.             elseif index == "close" then
  3194.                 closeCurrentTab()
  3195.             elseif index then
  3196.                 switchTab(index)
  3197.             end
  3198.  
  3199.             return true
  3200.         end
  3201.     end
  3202.  
  3203.     return false
  3204. end
  3205.  
  3206.  
  3207. local handleEvents = function()
  3208.     loadTab(1, startupSite)
  3209.     currentTab = 1
  3210.  
  3211.     while true do
  3212.         drawMenubar()
  3213.         local event = {os.pullEventRaw()}
  3214.         drawMenubar()
  3215.  
  3216.         local cancelEvent = false
  3217.         if event[1] == "terminate" then
  3218.             break
  3219.         elseif event[1] == "key" then
  3220.             cancelEvent = handleKeyDown(event)
  3221.         elseif event[1] == "mouse_click" then
  3222.             cancelEvent = handleMouseDown(event)
  3223.         elseif event[1] == websiteErrorEvent then
  3224.             cancelEvent = true
  3225.  
  3226.             loadTab(currentTab, tabs[currentTab].url, function()
  3227.                 builtInSites["crash"](event[2])
  3228.             end)
  3229.         elseif event[1] == redirectEvent then
  3230.             cancelEvent = true
  3231.  
  3232.             if (event[2]:match("^rdnt://(.+)$")) then
  3233.                 event[2] = event[2]:match("^rdnt://(.+)$")
  3234.             end
  3235.  
  3236.             loadTab(currentTab, event[2])
  3237.         end
  3238.  
  3239.         if not cancelEvent then
  3240.             term.redirect(tabs[currentTab].win)
  3241.             term.setCursorPos(tabs[currentTab].ox, tabs[currentTab].oy)
  3242.  
  3243.             coroutine.resume(tabs[currentTab].thread, unpack(event))
  3244.  
  3245.             local ox, oy = term.getCursorPos()
  3246.             tabs[currentTab].ox = ox
  3247.             tabs[currentTab].oy = oy
  3248.         end
  3249.     end
  3250. end
  3251.  
  3252.  
  3253.  
  3254. --    Main
  3255.  
  3256.  
  3257. local main = function()
  3258.     currentProtocol = "rdnt"
  3259.     currentTab = 1
  3260.  
  3261.     if term.isColor() then
  3262.         theme = colorTheme
  3263.         enableTabBar = true
  3264.     else
  3265.         theme = grayscaleTheme
  3266.         enableTabBar = false
  3267.     end
  3268.  
  3269.     setupMenubar()
  3270.     protocols[currentProtocol]["setup"]()
  3271.  
  3272.     clear(theme.background, theme.text)
  3273.     handleEvents()
  3274. end
  3275.  
  3276.  
  3277. local handleError = function(err)
  3278.     clear(theme.background, theme.text)
  3279.  
  3280.     fill(1, 3, w, 3, theme.subtle)
  3281.     term.setCursorPos(1, 4)
  3282.     center("Firewolf has crashed!")
  3283.  
  3284.     term.setBackgroundColor(theme.background)
  3285.     term.setCursorPos(1, 8)
  3286.     centerSplit(err, w - 4)
  3287.     print("\n")
  3288.     center("Please report this error to")
  3289.     center("Towtow10.")
  3290.     print("")
  3291.     center("Press any key to exit.")
  3292.  
  3293.     os.pullEvent("key")
  3294.     os.queueEvent("")
  3295.     os.pullEvent()
  3296. end
  3297.  
  3298. local _, err = pcall(main)
  3299. term.redirect(originalTerminal)
  3300.  
  3301. Modem.closeAll()
  3302.  
  3303. if err and not err:lower():find("terminate") then
  3304.     handleError(err)
  3305. end
  3306.  
  3307.  
  3308. clear(colors.black, colors.white)
  3309. center("Thanks for using Firewolf " .. version)
  3310. center("Made by GravityScore and 1lann and Towtow10")
  3311. print("")
Add Comment
Please, Sign In to add comment