From 4475d80db9a8eb0db0b7bb88cf2d76794073f46c Mon Sep 17 00:00:00 2001 From: Andrew Lee Date: Mon, 16 Nov 2020 15:31:33 -0500 Subject: Removed unnecessary stuff in programs --- .../Firewolf.bup/Contents/bits-UI/firewolf.lua | 3243 -------------------- 1 file changed, 3243 deletions(-) delete mode 100644 Programs/Firewolf.bup/Contents/bits-UI/firewolf.lua (limited to 'Programs/Firewolf.bup/Contents/bits-UI/firewolf.lua') diff --git a/Programs/Firewolf.bup/Contents/bits-UI/firewolf.lua b/Programs/Firewolf.bup/Contents/bits-UI/firewolf.lua deleted file mode 100644 index d1ba857..0000000 --- a/Programs/Firewolf.bup/Contents/bits-UI/firewolf.lua +++ /dev/null @@ -1,3243 +0,0 @@ - --- --- Firewolf --- Made by GravityScore and 1lann --- - - - --- Variables - - -local version = "3.5" -local build = 18 - -local w, h = term.getSize() - -local isMenubarOpen = true -local menubarWindow = nil - -local allowUnencryptedConnections = true -local enableTabBar = true - -local currentWebsiteURL = "" -local builtInSites = {} - -local currentProtocol = "" -local protocols = {} - -local currentTab = 1 -local maxTabs = 5 -local maxTabNameWidth = 8 -local tabs = {} - -local languages = {} - -local history = {} - -local publicDNSChannel = 9999 -local publicResponseChannel = 9998 -local responseID = 41738 - -local httpTimeout = 10 -local searchResultTimeout = 1 -local initiationTimeout = 2 -local animationInterval = 0.125 -local fetchTimeout = 3 -local serverLimitPerComputer = 1 - -local websiteErrorEvent = "firewolf_websiteErrorEvent" -local redirectEvent = "firewolf_redirectEvent" - -local baseURL = "https://raw.githubusercontent.com/1lann/Firewolf/master/src" -local buildURL = baseURL .. "/build.txt" -local firewolfURL = baseURL .. "/client.lua" -local serverURL = baseURL .. "/server.lua" - -local originalTerminal = term.current() - -local firewolfLocation = "/" .. shell.getRunningProgram() -local downloadsLocation = "/downloads" - - -local theme = {} - -local colorTheme = { - background = colors.gray, - accent = colors.red, - subtle = colors.orange, - - lightText = colors.gray, - text = colors.white, - errorText = colors.red, -} - -local grayscaleTheme = { - background = colors.black, - accent = colors.black, - subtle = colors.black, - - lightText = colors.white, - text = colors.white, - errorText = colors.white, -} - - - --- Utilities - - -local modifiedRead = function(properties) - local text = "" - local startX, startY = term.getCursorPos() - local pos = 0 - - local previousText = "" - local readHistory = nil - local historyPos = 0 - - if not properties then - properties = {} - end - - if properties.displayLength then - properties.displayLength = math.min(properties.displayLength, w - 2) - else - properties.displayLength = w - startX - 1 - end - - if properties.startingText then - text = properties.startingText - pos = text:len() - end - - if properties.history then - readHistory = {} - for k, v in pairs(properties.history) do - readHistory[k] = v - end - end - - if readHistory[1] == text then - table.remove(readHistory, 1) - end - - local draw = function(replaceCharacter) - local scroll = 0 - if properties.displayLength and pos > properties.displayLength then - scroll = pos - properties.displayLength - end - - local repl = replaceCharacter or properties.replaceCharacter - term.setTextColor(theme.text) - term.setCursorPos(startX, startY) - if repl then - term.write(string.rep(repl:sub(1, 1), text:len() - scroll)) - else - term.write(text:sub(scroll + 1)) - end - - term.setCursorPos(startX + pos - scroll, startY) - end - - term.setCursorBlink(true) - draw() - while true do - local event, key, x, y, param4, param5 = os.pullEvent() - - if properties.onEvent then - -- Actions: - -- - exit (bool) - -- - text - -- - nullifyText - - term.setCursorBlink(false) - local action = properties.onEvent(text, event, key, x, y, param4, param5) - if action then - if action.text then - draw(" ") - text = action.text - pos = text:len() - end if action.nullifyText then - text = nil - action.exit = true - end if action.exit then - break - end - end - draw() - end - - term.setCursorBlink(true) - if event == "char" then - local canType = true - if properties.maxLength and text:len() >= properties.maxLength then - canType = false - end - - if canType then - text = text:sub(1, pos) .. key .. text:sub(pos + 1, -1) - pos = pos + 1 - draw() - end - elseif event == "key" then - if key == keys.enter then - break - elseif key == keys.left and pos > 0 then - pos = pos - 1 - draw() - elseif key == keys.right and pos < text:len() then - pos = pos + 1 - draw() - elseif key == keys.backspace and pos > 0 then - draw(" ") - text = text:sub(1, pos - 1) .. text:sub(pos + 1, -1) - pos = pos - 1 - draw() - elseif key == keys.delete and pos < text:len() then - draw(" ") - text = text:sub(1, pos) .. text:sub(pos + 2, -1) - draw() - elseif key == keys.home then - pos = 0 - draw() - elseif key == keys["end"] then - pos = text:len() - draw() - elseif (key == keys.up or key == keys.down) and readHistory then - local shouldDraw = false - if historyPos == 0 then - previousText = text - elseif historyPos > 0 then - readHistory[historyPos] = text - end - - if key == keys.up then - if historyPos < #readHistory then - historyPos = historyPos + 1 - shouldDraw = true - end - else - if historyPos > 0 then - historyPos = historyPos - 1 - shouldDraw = true - end - end - - if shouldDraw then - draw(" ") - if historyPos > 0 then - text = readHistory[historyPos] - else - text = previousText - end - pos = text:len() - draw() - end - end - elseif event == "mouse_click" then - local scroll = 0 - if properties.displayLength and pos > properties.displayLength then - scroll = pos - properties.displayLength - end - - if y == startY and x >= startX and x <= math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then - pos = x - startX + scroll - draw() - elseif y == startY then - if x < startX then - pos = scroll - draw() - elseif x > math.min(startX + text:len(), startX + (properties.displayLength or 10000)) then - pos = text:len() - draw() - end - end - end - end - - term.setCursorBlink(false) - print("") - return text -end - - -local prompt = function(items, x, y, w, h) - local selected = 1 - local scroll = 0 - - local draw = function() - for i = scroll + 1, scroll + h do - local item = items[i] - if item then - term.setCursorPos(x, y + i - 1) - term.setBackgroundColor(theme.background) - term.setTextColor(theme.lightText) - - if scroll + selected == i then - term.setTextColor(theme.text) - term.write(" > ") - else - term.write(" - ") - end - - term.write(item) - end - end - end - - draw() - while true do - local event, key, x, y = os.pullEvent() - - if event == "key" then - if key == keys.up and selected > 1 then - selected = selected - 1 - - if selected - scroll == 0 then - scroll = scroll - 1 - end - elseif key == keys.down and selected < #items then - selected = selected + 1 - end - - draw() - elseif event == "mouse_click" then - - elseif event == "mouse_scroll" then - if key > 0 then - os.queueEvent("key", keys.down) - else - os.queueEvent("key", keys.up) - end - end - end -end - - - --- GUI - - -local clear = function(bg, fg) - term.setTextColor(fg) - term.setBackgroundColor(bg) - term.clear() - term.setCursorPos(1, 1) -end - - -local fill = function(x, y, width, height, bg) - term.setBackgroundColor(bg) - for i = y, y + height - 1 do - term.setCursorPos(x, i) - term.write(string.rep(" ", width)) - end -end - - -local center = function(text) - local x, y = term.getCursorPos() - term.setCursorPos(math.floor(w / 2 - text:len() / 2) + (text:len() % 2 == 0 and 1 or 0), y) - term.write(text) - term.setCursorPos(1, y + 1) -end - - -local centerSplit = function(text, width) - local words = {} - for word in text:gmatch("[^ \t]+") do - table.insert(words, word) - end - - local lines = {""} - while lines[#lines]:len() < width do - lines[#lines] = lines[#lines] .. words[1] .. " " - table.remove(words, 1) - - if #words == 0 then - break - end - - if lines[#lines]:len() + words[1]:len() >= width then - table.insert(lines, "") - end - end - - for _, line in pairs(lines) do - center(line) - end -end - - - --- Updating - - -local download = function(url) - http.request(url) - local timeoutID = os.startTimer(httpTimeout) - while true do - local event, fetchedURL, response = os.pullEvent() - if (event == "timer" and fetchedURL == timeoutID) or event == "http_failure" then - return false - elseif event == "http_success" and fetchedURL == url then - local contents = response.readAll() - response.close() - return contents - end - end -end - - -local downloadAndSave = function(url, path) - local contents = download(url) - if contents and not fs.isReadOnly(path) and not fs.isDir(path) then - local f = io.open(path, "w") - f:write(contents) - f:close() - return false - end - return true -end - - -local updateAvailable = function() - local number = download(buildURL) - if not number then - return false, true - end - - if number and tonumber(number) and tonumber(number) > build then - return true, false - end - - return false, false -end - - -local redownloadBrowser = function() - return downloadAndSave(firewolfURL, firewolfLocation) -end - - - --- Display Websites - - -builtInSites["display"] = {} - - -builtInSites["display"]["firewolf"] = function() - local logo = { - "______ _ __ ", - "| ___| | |/ _|", - "| |_ _ ____ _____ _____ | | |_ ", - "| _|| | __/ _ \\ \\ /\\ / / _ \\| | _|", - "| | | | | | __/\\ V V / <_> | | | ", - "\\_| |_|_| \\___| \\_/\\_/ \\___/|_|_| ", - } - - clear(theme.background, theme.text) - fill(1, 3, w, 9, theme.subtle) - - term.setCursorPos(1, 3) - for _, line in pairs(logo) do - center(line) - end - - term.setCursorPos(1, 10) - center(version) - - term.setBackgroundColor(theme.background) - term.setTextColor(theme.text) - term.setCursorPos(1, 14) - center("Search using the Query Box above") - center("Visit rdnt://help for help using Firewolf.") - - term.setCursorPos(1, h - 2) - center("Made by GravityScore and 1lann") -end - - -builtInSites["display"]["credits"] = function() - clear(theme.background, theme.text) - - fill(1, 6, w, 3, theme.subtle) - term.setCursorPos(1, 7) - center("Credits") - - term.setBackgroundColor(theme.background) - term.setCursorPos(1, 11) - center("Written by GravityScore and 1lann") - print("") - center("RC4 Implementation by AgentE382") -end - - -builtInSites["display"]["help"] = function() - clear(theme.background, theme.text) - - fill(1, 3, w, 3, theme.subtle) - term.setCursorPos(1, 4) - center("Help") - - term.setBackgroundColor(theme.background) - term.setCursorPos(1, 7) - center("Click on the URL bar or press control to") - center("open the query box") - print("") - center("Type in a search query or website URL") - center("into the query box.") - print("") - center("Search for nothing to see all available") - center("websites.") - print("") - center("Visit rdnt://server to setup a server.") - center("Visit rdnt://update to update Firewolf.") -end - - -builtInSites["display"]["server"] = function() - clear(theme.background, theme.text) - - fill(1, 6, w, 3, theme.subtle) - term.setCursorPos(1, 7) - center("Server Software") - - term.setBackgroundColor(theme.background) - term.setCursorPos(1, 11) - if not http then - center("HTTP is not enabled!") - print("") - center("Please enable it in your config file") - center("to download Firewolf Server.") - else - center("Press space to download") - center("Firewolf Server to:") - print("") - center("/fwserver") - - while true do - local event, key = os.pullEvent() - if event == "key" and key == 57 then - fill(1, 11, w, 4, theme.background) - term.setCursorPos(1, 11) - center("Downloading...") - - local err = downloadAndSave(serverURL, "/fwserver") - - fill(1, 11, w, 4, theme.background) - term.setCursorPos(1, 11) - center(err and "Download failed!" or "Download successful!") - end - end - end -end - - -builtInSites["display"]["update"] = function() - clear(theme.background, theme.text) - - fill(1, 3, w, 3, theme.subtle) - term.setCursorPos(1, 4) - center("Update") - - term.setBackgroundColor(theme.background) - if not http then - term.setCursorPos(1, 9) - center("HTTP is not enabled!") - print("") - center("Please enable it in your config") - center("file to download Firewolf updates.") - else - term.setCursorPos(1, 10) - center("Checking for updates...") - - local available, err = updateAvailable() - - term.setCursorPos(1, 10) - if available then - term.clearLine() - center("Update found!") - center("Press enter to download.") - - while true do - local event, key = os.pullEvent() - if event == "key" and key == keys.enter then - break - end - end - - fill(1, 10, w, 2, theme.background) - term.setCursorPos(1, 10) - center("Downloading...") - - local err = redownloadBrowser() - - term.setCursorPos(1, 10) - term.clearLine() - if err then - center("Download failed!") - else - center("Download succeeded!") - center("Please restart Firewolf...") - end - elseif err then - term.clearLine() - center("Checking failed!") - else - term.clearLine() - center("No updates found.") - end - end -end - - - --- Built In Websites - - -builtInSites["error"] = function(err) - fill(1, 3, w, 3, theme.subtle) - term.setCursorPos(1, 4) - center("Failed to load page!") - - term.setBackgroundColor(theme.background) - term.setCursorPos(1, 9) - center(err) - print("") - center("Please try again.") -end - - -builtInSites["noresults"] = function() - fill(1, 3, w, 3, theme.subtle) - term.setCursorPos(1, 4) - center("No results!") - - term.setBackgroundColor(theme.background) - term.setCursorPos(1, 9) - center("Your search didn't return") - center("any results!") - - os.pullEvent("key") - os.queueEvent("") - os.pullEvent() -end - - -builtInSites["search advanced"] = function(results) - local startY = 6 - local height = h - startY - 1 - local scroll = 0 - - local draw = function() - fill(1, startY, w, height + 1, theme.background) - - for i = scroll + 1, scroll + height do - if results[i] then - term.setCursorPos(5, (i - scroll) + startY) - term.write(currentProtocol .. "://" .. results[i]) - end - end - end - - draw() - while true do - local event, but, x, y = os.pullEvent() - - if event == "mouse_click" and y >= startY and y <= startY + height then - local item = results[y - startY + scroll] - if item then - os.queueEvent(redirectEvent, item) - coroutine.yield() - end - elseif event == "key" then - if but == keys.up then - scroll = math.max(0, scroll - 1) - elseif but == keys.down and #results > height then - scroll = math.min(scroll + 1, #results - height) - end - - draw() - elseif event == "mouse_scroll" then - if but > 0 then - os.queueEvent("key", keys.down) - else - os.queueEvent("key", keys.up) - end - end - end -end - - -builtInSites["search basic"] = function(results) - local startY = 6 - local height = h - startY - 1 - local scroll = 0 - local selected = 1 - - local draw = function() - fill(1, startY, w, height + 1, theme.background) - - for i = scroll + 1, scroll + height do - if results[i] then - if i == selected + scroll then - term.setCursorPos(3, (i - scroll) + startY) - term.write("> " .. currentProtocol .. "://" .. results[i]) - else - term.setCursorPos(5, (i - scroll) + startY) - term.write(currentProtocol .. "://" .. results[i]) - end - end - end - end - - draw() - while true do - local event, but, x, y = os.pullEvent() - - if event == "key" then - if but == keys.up and selected + scroll > 1 then - if selected > 1 then - selected = selected - 1 - else - scroll = math.max(0, scroll - 1) - end - elseif but == keys.down and selected + scroll < #results then - if selected < height then - selected = selected + 1 - else - scroll = math.min(scroll + 1, #results - height) - end - elseif but == keys.enter then - local item = results[scroll + selected] - if item then - os.queueEvent(redirectEvent, item) - coroutine.yield() - end - end - - draw() - elseif event == "mouse_scroll" then - if but > 0 then - os.queueEvent("key", keys.down) - else - os.queueEvent("key", keys.up) - end - end - end -end - - -builtInSites["search"] = function(results) - clear(theme.background, theme.text) - - fill(1, 3, w, 3, theme.subtle) - term.setCursorPos(1, 4) - center(#results .. " Search " .. (#results == 1 and "Result" or "Results")) - - term.setBackgroundColor(theme.background) - - if term.isColor() then - builtInSites["search advanced"](results) - else - builtInSites["search basic"](results) - end -end - - -builtInSites["crash"] = function(err) - fill(1, 3, w, 3, theme.subtle) - term.setCursorPos(1, 4) - center("The website crashed!") - - term.setBackgroundColor(theme.background) - term.setCursorPos(1, 8) - centerSplit(err, w - 4) - print("\n") - center("Please report this error to") - center("the website creator.") -end - - - --- Menubar - - -local getTabName = function(url) - local name = url:match("^[^/]+") - - if not name then - name = "Search" - end - - if name:sub(1, 3) == "www" then - name = name:sub(5):gsub("^%s*(.-)%s*$", "%1") - end - - if name:len() > maxTabNameWidth then - name = name:sub(1, maxTabNameWidth):gsub("^%s*(.-)%s*$", "%1") - end - - if name:sub(-1, -1) == "." then - name = name:sub(1, -2):gsub("^%s*(.-)%s*$", "%1") - end - - return name:gsub("^%s*(.-)%s*$", "%1") -end - - -local determineClickedTab = function(x, y) - if y == 2 then - local minx = 2 - for i, tab in pairs(tabs) do - local name = getTabName(tab.url) - - if x >= minx and x <= minx + name:len() - 1 then - return i - elseif x == minx + name:len() and i == currentTab and #tabs > 1 then - return "close" - else - minx = minx + name:len() + 2 - end - end - - if x == minx and #tabs < maxTabs then - return "new" - end - end - - return nil -end - - -local setupMenubar = function() - if enableTabBar then - menubarWindow = window.create(originalTerminal, 1, 1, w, 2, false) - else - menubarWindow = window.create(originalTerminal, 1, 1, w, 1, false) - end -end - - -local drawMenubar = function() - if isMenubarOpen then - term.redirect(menubarWindow) - menubarWindow.setVisible(true) - - fill(1, 1, w, 1, theme.accent) - term.setTextColor(theme.text) - - term.setBackgroundColor(theme.accent) - term.setCursorPos(2, 1) - if currentWebsiteURL:match("^[^%?]+") then - term.write(currentProtocol .. "://" .. currentWebsiteURL:match("^[^%?]+")) - else - term.write(currentProtocol .. "://" ..currentWebsiteURL) - end - - term.setCursorPos(w - 5, 1) - term.write("[===]") - - if enableTabBar then - fill(1, 2, w, 1, theme.subtle) - - term.setCursorPos(1, 2) - for i, tab in pairs(tabs) do - term.setBackgroundColor(theme.subtle) - term.setTextColor(theme.lightText) - if i == currentTab then - term.setTextColor(theme.text) - end - - local tabName = getTabName(tab.url) - term.write(" " .. tabName) - - if i == currentTab and #tabs > 1 then - term.setTextColor(theme.errorText) - term.write("x") - else - term.write(" ") - end - end - - if #tabs < maxTabs then - term.setTextColor(theme.lightText) - term.setBackgroundColor(theme.subtle) - term.write(" + ") - end - end - else - menubarWindow.setVisible(false) - end -end - - - --- RC4 --- Implementation by AgentE382 - - -local cryptWrapper = function(plaintext, salt) - local key = type(salt) == "table" and {unpack(salt)} or {string.byte(salt, 1, #salt)} - local S = {} - for i = 0, 255 do - S[i] = i - end - - local j, keylength = 0, #key - for i = 0, 255 do - j = (j + S[i] + key[i % keylength + 1]) % 256 - S[i], S[j] = S[j], S[i] - end - - local i = 0 - j = 0 - local chars, astable = type(plaintext) == "table" and {unpack(plaintext)} or {string.byte(plaintext, 1, #plaintext)}, false - - for n = 1, #chars do - i = (i + 1) % 256 - j = (j + S[i]) % 256 - S[i], S[j] = S[j], S[i] - chars[n] = bit.bxor(S[(S[i] + S[j]) % 256], chars[n]) - if chars[n] > 127 or chars[n] == 13 then - astable = true - end - end - - return astable and chars or string.char(unpack(chars)) -end - - -local crypt = function(text, key) - local resp, msg = pcall(cryptWrapper, text, key) - if resp then - return msg - else - return nil - end -end - - - --- Base64 --- --- Base64 Encryption/Decryption --- By KillaVanilla --- http://www.computercraft.info/forums2/index.php?/topic/12450-killavanillas-various-apis/ --- http://pastebin.com/rCYDnCxn --- - - -local alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - - -local function sixBitToBase64(input) - return string.sub(alphabet, input+1, input+1) -end - - -local function base64ToSixBit(input) - for i=1, 64 do - if input == string.sub(alphabet, i, i) then - return i-1 - end - end -end - - -local function octetToBase64(o1, o2, o3) - local shifted = bit.brshift(bit.band(o1, 0xFC), 2) - local i1 = sixBitToBase64(shifted) - local i2 = "A" - local i3 = "=" - local i4 = "=" - if o2 then - i2 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o1, 3), 4), bit.brshift(bit.band(o2, 0xF0), 4) )) - if not o3 then - i3 = sixBitToBase64(bit.blshift(bit.band(o2, 0x0F), 2)) - else - i3 = sixBitToBase64(bit.bor( bit.blshift(bit.band(o2, 0x0F), 2), bit.brshift(bit.band(o3, 0xC0), 6) )) - end - else - i2 = sixBitToBase64(bit.blshift(bit.band(o1, 3), 4)) - end - if o3 then - i4 = sixBitToBase64(bit.band(o3, 0x3F)) - end - - return i1..i2..i3..i4 -end - - -local function base64ToThreeOctet(s1) - local c1 = base64ToSixBit(string.sub(s1, 1, 1)) - local c2 = base64ToSixBit(string.sub(s1, 2, 2)) - local c3 = 0 - local c4 = 0 - local o1 = 0 - local o2 = 0 - local o3 = 0 - if string.sub(s1, 3, 3) == "=" then - c3 = nil - c4 = nil - elseif string.sub(s1, 4, 4) == "=" then - c3 = base64ToSixBit(string.sub(s1, 3, 3)) - c4 = nil - else - c3 = base64ToSixBit(string.sub(s1, 3, 3)) - c4 = base64ToSixBit(string.sub(s1, 4, 4)) - end - o1 = bit.bor( bit.blshift(c1, 2), bit.brshift(bit.band( c2, 0x30 ), 4) ) - if c3 then - o2 = bit.bor( bit.blshift(bit.band(c2, 0x0F), 4), bit.brshift(bit.band( c3, 0x3C ), 2) ) - else - o2 = nil - end - if c4 then - o3 = bit.bor( bit.blshift(bit.band(c3, 3), 6), c4 ) - else - o3 = nil - end - return o1, o2, o3 -end - - -local function splitIntoBlocks(bytes) - local blockNum = 1 - local blocks = {} - for i=1, #bytes, 3 do - blocks[blockNum] = {bytes[i], bytes[i+1], bytes[i+2]} - blockNum = blockNum+1 - end - return blocks -end - - -function base64Encode(bytes) - local blocks = splitIntoBlocks(bytes) - local output = "" - for i=1, #blocks do - output = output..octetToBase64( unpack(blocks[i]) ) - end - return output -end - - -function base64Decode(str) - local bytes = {} - local blocks = {} - local blockNum = 1 - - for i=1, #str, 4 do - blocks[blockNum] = string.sub(str, i, i+3) - blockNum = blockNum+1 - end - - for i=1, #blocks do - local o1, o2, o3 = base64ToThreeOctet(blocks[i]) - table.insert(bytes, o1) - table.insert(bytes, o2) - table.insert(bytes, o3) - end - - return bytes -end - - - --- SHA-256 --- --- Adaptation of the Secure Hashing Algorithm (SHA-244/256) --- Found Here: http://lua-users.org/wiki/SecureHashAlgorithm --- --- Using an adapted version of the bit library --- Found Here: https://bitbucket.org/Boolsheet/bslf/src/1ee664885805/bit.lua - - -local MOD = 2^32 -local MODM = MOD-1 - - -local function memoize(f) - local mt = {} - local t = setmetatable({}, mt) - function mt:__index(k) - local v = f(k) - t[k] = v - return v - end - return t -end - - -local function make_bitop_uncached(t, m) - local function bitop(a, b) - local res,p = 0,1 - while a ~= 0 and b ~= 0 do - local am, bm = a % m, b % m - res = res + t[am][bm] * p - a = (a - am) / m - b = (b - bm) / m - p = p * m - end - res = res + (a + b) * p - return res - end - - return bitop -end - - -local function make_bitop(t) - local op1 = make_bitop_uncached(t,2^1) - local op2 = memoize(function(a) - return memoize(function(b) - return op1(a, b) - end) - end) - return make_bitop_uncached(op2, 2 ^ (t.n or 1)) -end - - -local customBxor1 = make_bitop({[0] = {[0] = 0,[1] = 1}, [1] = {[0] = 1, [1] = 0}, n = 4}) - -local function customBxor(a, b, c, ...) - local z = nil - if b then - a = a % MOD - b = b % MOD - z = customBxor1(a, b) - if c then - z = customBxor(z, c, ...) - end - return z - elseif a then - return a % MOD - else - return 0 - end -end - - -local function customBand(a, b, c, ...) - local z - if b then - a = a % MOD - b = b % MOD - z = ((a + b) - customBxor1(a,b)) / 2 - if c then - z = customBand(z, c, ...) - end - return z - elseif a then - return a % MOD - else - return MODM - end -end - - -local function bnot(x) - return (-1 - x) % MOD -end - - -local function rshift1(a, disp) - if disp < 0 then - return lshift(a, -disp) - end - return math.floor(a % 2 ^ 32 / 2 ^ disp) -end - - -local function rshift(x, disp) - if disp > 31 or disp < -31 then - return 0 - end - return rshift1(x % MOD, disp) -end - - -local function lshift(a, disp) - if disp < 0 then - return rshift(a, -disp) - end - return (a * 2 ^ disp) % 2 ^ 32 -end - - -local function rrotate(x, disp) - x = x % MOD - disp = disp % 32 - local low = customBand(x, 2 ^ disp - 1) - return rshift(x, disp) + lshift(low, 32 - disp) -end - - -local k = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, - 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, - 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, - 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, - 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, - 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, -} - - -local function str2hexa(s) - return (string.gsub(s, ".", function(c) - return string.format("%02x", string.byte(c)) - end)) -end - - -local function num2s(l, n) - local s = "" - for i = 1, n do - local rem = l % 256 - s = string.char(rem) .. s - l = (l - rem) / 256 - end - return s -end - - -local function s232num(s, i) - local n = 0 - for i = i, i + 3 do - n = n*256 + string.byte(s, i) - end - return n -end - - -local function preproc(msg, len) - local extra = 64 - ((len + 9) % 64) - len = num2s(8 * len, 8) - msg = msg .. "\128" .. string.rep("\0", extra) .. len - assert(#msg % 64 == 0) - return msg -end - - -local function initH256(H) - H[1] = 0x6a09e667 - H[2] = 0xbb67ae85 - H[3] = 0x3c6ef372 - H[4] = 0xa54ff53a - H[5] = 0x510e527f - H[6] = 0x9b05688c - H[7] = 0x1f83d9ab - H[8] = 0x5be0cd19 - return H -end - - -local function digestblock(msg, i, H) - local w = {} - for j = 1, 16 do - w[j] = s232num(msg, i + (j - 1)*4) - end - for j = 17, 64 do - local v = w[j - 15] - local s0 = customBxor(rrotate(v, 7), rrotate(v, 18), rshift(v, 3)) - v = w[j - 2] - w[j] = w[j - 16] + s0 + w[j - 7] + customBxor(rrotate(v, 17), rrotate(v, 19), rshift(v, 10)) - end - - 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] - for i = 1, 64 do - local s0 = customBxor(rrotate(a, 2), rrotate(a, 13), rrotate(a, 22)) - local maj = customBxor(customBand(a, b), customBand(a, c), customBand(b, c)) - local t2 = s0 + maj - local s1 = customBxor(rrotate(e, 6), rrotate(e, 11), rrotate(e, 25)) - local ch = customBxor (customBand(e, f), customBand(bnot(e), g)) - local t1 = h + s1 + ch + k[i] + w[i] - h, g, f, e, d, c, b, a = g, f, e, d + t1, c, b, a, t1 + t2 - end - - H[1] = customBand(H[1] + a) - H[2] = customBand(H[2] + b) - H[3] = customBand(H[3] + c) - H[4] = customBand(H[4] + d) - H[5] = customBand(H[5] + e) - H[6] = customBand(H[6] + f) - H[7] = customBand(H[7] + g) - H[8] = customBand(H[8] + h) -end - - -local function sha256(msg) - msg = preproc(msg, #msg) - local H = initH256({}) - for i = 1, #msg, 64 do - digestblock(msg, i, H) - end - return str2hexa(num2s(H[1], 4) .. num2s(H[2], 4) .. num2s(H[3], 4) .. num2s(H[4], 4) .. - num2s(H[5], 4) .. num2s(H[6], 4) .. num2s(H[7], 4) .. num2s(H[8], 4)) -end - - -local protocolName = "Firewolf" - - - --- Cryptography - - -local Cryptography = {} -Cryptography.sha = {} -Cryptography.base64 = {} -Cryptography.aes = {} - - -function Cryptography.bytesFromMessage(msg) - local bytes = {} - - for i = 1, msg:len() do - local letter = string.byte(msg:sub(i, i)) - table.insert(bytes, letter) - end - - return bytes -end - - -function Cryptography.messageFromBytes(bytes) - local msg = "" - - for i = 1, #bytes do - local letter = string.char(bytes[i]) - msg = msg .. letter - end - - return msg -end - - -function Cryptography.bytesFromKey(key) - local bytes = {} - - for i = 1, key:len() / 2 do - local group = key:sub((i - 1) * 2 + 1, (i - 1) * 2 + 1) - local num = tonumber(group, 16) - table.insert(bytes, num) - end - - return bytes -end - - -function Cryptography.sha.sha256(msg) - return sha256(msg) -end - - -function Cryptography.aes.encrypt(msg, key) - return base64Encode(crypt(msg, key)) -end - - -function Cryptography.aes.decrypt(msg, key) - return crypt(base64Decode(msg), key) -end - - -function Cryptography.base64.encode(msg) - return base64Encode(Cryptography.bytesFromMessage(msg)) -end - - -function Cryptography.base64.decode(msg) - return Cryptography.messageFromBytes(base64Decode(msg)) -end - - -function Cryptography.channel(text) - local hashed = Cryptography.sha.sha256(text) - - local total = 0 - - for i = 1, hashed:len() do - total = total + string.byte(hashed:sub(i, i)) - end - - return (total % 55530) + 10000 -end - - -function Cryptography.sanatize(text) - local sanatizeChars = {"%", "(", ")", "[", "]", ".", "+", "-", "*", "?", "^", "$"} - - for _, char in pairs(sanatizeChars) do - text = text:gsub("%"..char, "%%%"..char) - end - return text -end - - - --- Modem - - -local Modem = {} -Modem.modems = {} - - -function Modem.exists() - Modem.exists = false - for _, side in pairs(rs.getSides()) do - if peripheral.isPresent(side) and peripheral.getType(side) == "modem" then - Modem.exists = true - - if not Modem.modems[side] then - Modem.modems[side] = peripheral.wrap(side) - end - end - end - - return Modem.exists -end - - -function Modem.open(channel) - if not Modem.exists then - return false - end - - for side, modem in pairs(Modem.modems) do - modem.open(channel) - rednet.open(side) - end - - return true -end - - -function Modem.close(channel) - if not Modem.exists then - return false - end - - for side, modem in pairs(Modem.modems) do - modem.close(channel) - end - - return true -end - - -function Modem.closeAll() - if not Modem.exists then - return false - end - - for side, modem in pairs(Modem.modems) do - modem.closeAll() - end - - return true -end - - -function Modem.isOpen(channel) - if not Modem.exists then - return false - end - - local isOpen = false - for side, modem in pairs(Modem.modems) do - if modem.isOpen(channel) then - isOpen = true - break - end - end - - return isOpen -end - - -function Modem.transmit(channel, msg) - if not Modem.exists then - return false - end - - if not Modem.isOpen(channel) then - Modem.open(channel) - end - - for side, modem in pairs(Modem.modems) do - modem.transmit(channel, channel, msg) - end - - return true -end - - - --- Handshake - - -local Handshake = {} - -Handshake.prime = 625210769 -Handshake.channel = 54569 -Handshake.base = -1 -Handshake.secret = -1 -Handshake.sharedSecret = -1 -Handshake.packetHeader = "["..protocolName.."-Handshake-Packet-Header]" -Handshake.packetMatch = "%["..protocolName.."%-Handshake%-Packet%-Header%](.+)" - - -function Handshake.exponentWithModulo(base, exponent, modulo) - local remainder = base - - for i = 1, exponent-1 do - remainder = remainder * remainder - if remainder >= modulo then - remainder = remainder % modulo - end - end - - return remainder -end - - -function Handshake.clear() - Handshake.base = -1 - Handshake.secret = -1 - Handshake.sharedSecret = -1 -end - - -function Handshake.generateInitiatorData() - Handshake.base = math.random(10,99999) - Handshake.secret = math.random(10,99999) - return { - type = "initiate", - prime = Handshake.prime, - base = Handshake.base, - moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime) - } -end - - -function Handshake.generateResponseData(initiatorData) - local isPrimeANumber = type(initiatorData.prime) == "number" - local isPrimeMatching = initiatorData.prime == Handshake.prime - local isBaseANumber = type(initiatorData.base) == "number" - local isInitiator = initiatorData.type == "initiate" - local isModdedSecretANumber = type(initiatorData.moddedSecret) == "number" - local areAllNumbersNumbers = isPrimeANumber and isBaseANumber and isModdedSecretANumber - - if areAllNumbersNumbers and isPrimeMatching then - if isInitiator then - Handshake.base = initiatorData.base - Handshake.secret = math.random(10,99999) - Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime) - return { - type = "response", - prime = Handshake.prime, - base = Handshake.base, - moddedSecret = Handshake.exponentWithModulo(Handshake.base, Handshake.secret, Handshake.prime) - }, Handshake.sharedSecret - elseif initiatorData.type == "response" and Handshake.base > 0 and Handshake.secret > 0 then - Handshake.sharedSecret = Handshake.exponentWithModulo(initiatorData.moddedSecret, Handshake.secret, Handshake.prime) - return Handshake.sharedSecret - else - return false - end - else - return false - end -end - - - --- Secure Connection - - -local SecureConnection = {} -SecureConnection.__index = SecureConnection - - -SecureConnection.packetHeaderA = "["..protocolName.."-" -SecureConnection.packetHeaderB = "-SecureConnection-Packet-Header]" -SecureConnection.packetMatchA = "%["..protocolName.."%-" -SecureConnection.packetMatchB = "%-SecureConnection%-Packet%-Header%](.+)" -SecureConnection.connectionTimeout = 0.1 -SecureConnection.successPacketTimeout = 0.1 - - -function SecureConnection.new(secret, key, identifier, distance, isRednet) - local self = setmetatable({}, SecureConnection) - self:setup(secret, key, identifier, distance, isRednet) - return self -end - - -function SecureConnection:setup(secret, key, identifier, distance, isRednet) - local rawSecret - - if isRednet then - self.isRednet = true - self.distance = -1 - self.rednet_id = distance - rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) .. - "|" .. tostring(key) .. "|rednet" - else - self.isRednet = false - self.distance = distance - rawSecret = protocolName .. "|" .. tostring(secret) .. "|" .. tostring(identifier) .. - "|" .. tostring(key) .. "|" .. tostring(distance) - end - - self.identifier = identifier - self.packetMatch = SecureConnection.packetMatchA .. Cryptography.sanatize(identifier) .. SecureConnection.packetMatchB - self.packetHeader = SecureConnection.packetHeaderA .. identifier .. SecureConnection.packetHeaderB - self.secret = Cryptography.sha.sha256(rawSecret) - self.channel = Cryptography.channel(self.secret) - - if not self.isRednet then - Modem.open(self.channel) - end -end - - -function SecureConnection:verifyHeader(msg) - if msg:match(self.packetMatch) then - return true - else - return false - end -end - - -function SecureConnection:sendMessage(msg, rednetProtocol) - local rawEncryptedMsg = Cryptography.aes.encrypt(self.packetHeader .. msg, self.secret) - local encryptedMsg = self.packetHeader .. rawEncryptedMsg - - if self.isRednet then - rednet.send(self.rednet_id, encryptedMsg, rednetProtocol) - return true - else - return Modem.transmit(self.channel, encryptedMsg) - end -end - - -function SecureConnection:decryptMessage(msg) - if self:verifyHeader(msg) then - local encrypted = msg:match(self.packetMatch) - - local unencryptedMsg = nil - pcall(function() unencryptedMsg = Cryptography.aes.decrypt(encrypted, self.secret) end) - if not unencryptedMsg then - return false, "Could not decrypt" - end - - if self:verifyHeader(unencryptedMsg) then - return true, unencryptedMsg:match(self.packetMatch) - else - return false, "Could not verify" - end - else - return false, "Could not stage 1 verify" - end -end - - - --- RDNT Protocol - - -protocols["rdnt"] = {} - -local header = {} -header.dnsPacket = "[Firewolf-DNS-Packet]" -header.dnsHeaderMatch = "^%[Firewolf%-DNS%-Response%](.+)$" -header.rednetHeader = "[Firewolf-Rednet-Channel-Simulation]" -header.rednetMatch = "^%[Firewolf%-Rednet%-Channel%-Simulation%](%d+)$" -header.responseMatchA = "^%[Firewolf%-" -header.responseMatchB = "%-" -header.responseMatchC = "%-Handshake%-Response%](.+)$" -header.requestHeaderA = "[Firewolf-" -header.requestHeaderB = "-Handshake-Request]" -header.pageRequestHeaderA = "[Firewolf-" -header.pageRequestHeaderB = "-Page-Request]" -header.pageResponseMatchA = "^%[Firewolf%-" -header.pageResponseMatchB = "%-Page%-Response%]%[HEADER%](.-)%[BODY%](.+)$" -header.closeHeaderA = "[Firewolf-" -header.closeHeaderB = "-Connection-Close]" - - -protocols["rdnt"]["setup"] = function() - if not Modem.exists() then - error("No modem found!") - end -end - - -protocols["rdnt"]["fetchAllSearchResults"] = function() - Modem.open(publicDNSChannel) - Modem.open(publicResponseChannel) - Modem.transmit(publicDNSChannel, header.dnsPacket) - Modem.close(publicDNSChannel) - - rednet.broadcast(header.dnsPacket, header.rednetHeader .. publicDNSChannel) - - local uniqueServers = {} - local uniqueDomains = {} - - local timer = os.startTimer(searchResultTimeout) - - while true do - local event, id, channel, protocol, message, dist = os.pullEventRaw() - if event == "modem_message" then - if channel == publicResponseChannel and message:match(header.dnsHeaderMatch) then - if not uniqueServers[tostring(dist)] then - uniqueServers[tostring(dist)] = true - local domain = message:match(header.dnsHeaderMatch) - if not uniqueDomains[domain] then - if not(domain:find("/") or domain:find(":") or domain:find("%?")) and #domain > 4 then - timer = os.startTimer(searchResultTimeout) - uniqueDomains[message:match(header.dnsHeaderMatch)] = tostring(dist) - end - end - end - end - elseif event == "rednet_message" and allowUnencryptedConnections then - if protocol and tonumber(protocol:match(header.rednetMatch)) == publicResponseChannel and channel:match(header.dnsHeaderMatch) then - if not uniqueServers[tostring(id)] then - uniqueServers[tostring(id)] = true - local domain = channel:match(header.dnsHeaderMatch) - if not uniqueDomains[domain] then - if not(domain:find("/") or domain:find(":") or domain:find("%?")) and #domain > 4 then - timer = os.startTimer(searchResultTimeout) - uniqueDomains[domain] = tostring(id) - end - end - end - end - elseif event == "timer" and id == timer then - local results = {} - for k, _ in pairs(uniqueDomains) do - table.insert(results, k) - end - - return results - end - end -end - - -protocols["rdnt"]["fetchConnectionObject"] = function(url) - local serverChannel = Cryptography.channel(url) - local requestHeader = header.requestHeaderA .. url .. header.requestHeaderB - local responseMatch = header.responseMatchA .. Cryptography.sanatize(url) .. header.responseMatchB - - local serializedHandshake = textutils.serialize(Handshake.generateInitiatorData()) - - local rednetResults = {} - local directResults = {} - - local disconnectOthers = function(ignoreDirect) - for k,v in pairs(rednetResults) do - v.close() - end - for k,v in pairs(directResults) do - if k ~= ignoreDirect then - v.close() - end - end - end - - local timer = os.startTimer(initiationTimeout) - - Modem.open(serverChannel) - Modem.transmit(serverChannel, requestHeader .. serializedHandshake) - - rednet.broadcast(requestHeader .. serializedHandshake, header.rednetHeader .. serverChannel) - - -- Extendable to have server selection - - while true do - local event, id, channel, protocol, message, dist = os.pullEventRaw() - if event == "modem_message" then - local fullMatch = responseMatch .. tostring(dist) .. header.responseMatchC - if channel == serverChannel and message:match(fullMatch) and type(textutils.unserialize(message:match(fullMatch))) == "table" then - local key = Handshake.generateResponseData(textutils.unserialize(message:match(fullMatch))) - if key then - local connection = SecureConnection.new(key, url, url, dist) - table.insert(directResults, { - connection = connection, - fetchPage = function(page) - if not connection then - return nil - end - - local fetchTimer = os.startTimer(fetchTimeout) - - local pageRequest = header.pageRequestHeaderA .. url .. header.pageRequestHeaderB .. page - local pageResponseMatch = header.pageResponseMatchA .. Cryptography.sanatize(url) .. header.pageResponseMatchB - - connection:sendMessage(pageRequest, header.rednetHeader .. connection.channel) - - while true do - local event, id, channel, protocol, message, dist = os.pullEventRaw() - if event == "modem_message" and channel == connection.channel and connection:verifyHeader(message) then - local resp, data = connection:decryptMessage(message) - if not resp then - -- Decryption error - elseif data and data ~= page then - if data:match(pageResponseMatch) then - local head, body = data:match(pageResponseMatch) - return body, textutils.unserialize(head) - end - end - elseif event == "timer" and id == fetchTimer then - return nil - end - end - end, - close = function() - if connection ~= nil then - connection:sendMessage(header.closeHeaderA .. url .. header.closeHeaderB, header.rednetHeader..connection.channel) - Modem.close(connection.channel) - connection = nil - end - end - }) - - disconnectOthers(1) - return directResults[1] - end - end - elseif event == "rednet_message" then - local fullMatch = responseMatch .. os.getComputerID() .. header.responseMatchC - if protocol and tonumber(protocol:match(header.rednetMatch)) == serverChannel and channel:match(fullMatch) and type(textutils.unserialize(channel:match(fullMatch))) == "table" then - local key = Handshake.generateResponseData(textutils.unserialize(channel:match(fullMatch))) - if key then - local connection = SecureConnection.new(key, url, url, id, true) - table.insert(rednetResults, { - connection = connection, - fetchPage = function(page) - if not connection then - return nil - end - - local fetchTimer = os.startTimer(fetchTimeout) - - local pageRequest = header.pageRequestHeaderA .. url .. header.pageRequestHeaderB .. page - local pageResponseMatch = header.pageResponseMatchA .. Cryptography.sanatize(url) .. header.pageResponseMatchB - - connection:sendMessage(pageRequest, header.rednetHeader .. connection.channel) - - while true do - local event, id, channel, protocol, message, dist = os.pullEventRaw() - if event == "rednet_message" and protocol and tonumber(protocol:match(header.rednetMatch)) == connection.channel and connection:verifyHeader(channel) then - local resp, data = connection:decryptMessage(channel) - if not resp then - -- Decryption error - elseif data and data ~= page then - if data:match(pageResponseMatch) then - local head, body = data:match(pageResponseMatch) - return body, textutils.unserialize(head) - end - end - elseif event == "timer" and id == fetchTimer then - return nil - end - end - end, - close = function() - connection:sendMessage(header.closeHeaderA .. url .. header.closeHeaderB, header.rednetHeader..connection.channel) - Modem.close(connection.channel) - connection = nil - end - }) - - if #rednetResults == 1 then - timer = os.startTimer(0.2) - end - end - end - elseif event == "timer" and id == timer then - -- Return - if #directResults > 0 then - disconnectOthers(1) - return directResults[1] - elseif #rednetResults > 0 then - local lowestID = math.huge - local lowestResult = nil - for k,v in pairs(rednetResults) do - if v.connection.rednet_id < lowestID then - lowestID = v.connection.rednet_id - lowestResult = v - end - end - - for k,v in pairs(rednetResults) do - if v.connection.rednet_id ~= lowestID then - v.close() - end - end - - return lowestResult - else - return nil - end - end - end -end - - - --- Fetching Raw Data - - -local fetchSearchResultsForQuery = function(query) - local all = protocols[currentProtocol]["fetchAllSearchResults"]() - local results = {} - if query and query:len() > 0 then - for _, v in pairs(all) do - if v:find(query:lower()) then - table.insert(results, v) - end - end - else - results = all - end - - table.sort(results) - return results -end - - -local getConnectionObjectFromURL = function(url) - local domain = url:match("^([^/]+)") - return protocols[currentProtocol]["fetchConnectionObject"](domain) -end - - -local determineLanguage = function(header) - if type(header) == "table" then - if header.language and header.language == "Firewolf Markup" then - return "fwml" - else - return "lua" - end - else - return "lua" - end -end - - - --- History - - -local appendToHistory = function(url) - if history[1] ~= url then - table.insert(history, 1, url) - end -end - - - --- Fetch Websites - - -local loadingAnimation = function() - local state = -2 - - term.setTextColor(theme.text) - term.setBackgroundColor(theme.accent) - - term.setCursorPos(w - 5, 1) - term.write("[= ]") - - local timer = os.startTimer(animationInterval) - - while true do - local event, timerID = os.pullEvent() - if event == "timer" and timerID == timer then - term.setTextColor(theme.text) - term.setBackgroundColor(theme.accent) - - state = state + 1 - - term.setCursorPos(w - 5, 1) - term.write("[ ]") - term.setCursorPos(w - 2 - math.abs(state), 1) - term.write("=") - - if state == 2 then - state = -2 - end - - timer = os.startTimer(animationInterval) - end - end -end - - -local normalizeURL = function(url) - url = url:lower():gsub(" ", "") - if url == "home" or url == "homepage" then - url = "firewolf" - end - - return url -end - - -local normalizePage = function(page) - if not page then page = "" end - page = page:lower() - if page == "" then - page = "/" - end - return page -end - - -local determineActionForURL = function(url) - if url:len() > 0 and url:gsub("/", ""):len() == 0 then - return "none" - end - - if url == "exit" then - return "exit" - elseif builtInSites["display"][url] then - return "internal website" - elseif url == "" then - local results = fetchSearchResultsForQuery() - if #results > 0 then - return "search", results - else - return "none" - end - else - local connection = getConnectionObjectFromURL(url) - if connection then - return "external website", connection - else - local results = fetchSearchResultsForQuery(url) - if #results > 0 then - return "search", results - else - return "none" - end - end - end -end - - -local fetchSearch = function(url, results) - return languages["lua"]["runWithoutAntivirus"](builtInSites["search"], results) -end - - -local fetchInternal = function(url) - return languages["lua"]["runWithoutAntivirus"](builtInSites["display"][url]) -end - - -local fetchError = function(err) - return languages["lua"]["runWithoutAntivirus"](builtInSites["error"], err) -end - - -local fetchExternal = function(url, connection) - if connection.multipleServers then - -- Please forgive me - -- GravityScore forced me to do it like this - -- I don't mean it, I really don't. - connection = connection.servers[1] - end - - local page = normalizePage(url:match("^[^/]+/(.+)")) - local contents, head = connection.fetchPage(page) - if contents then - if type(contents) ~= "string" then - return fetchNone() - else - local language = determineLanguage(head) - return languages[language]["run"](contents, page, connection) - end - else - if connection then - connection.close() - return "retry" - end - return fetchError("A connection error/timeout has occurred!") - end -end - - -local fetchNone = function() - return languages["lua"]["runWithoutAntivirus"](builtInSites["noresults"]) -end - - -local fetchURL = function(url, inheritConnection) - url = normalizeURL(url) - currentWebsiteURL = url - - if inheritConnection then - local resp = fetchExternal(url, inheritConnection) - if resp ~= "retry" then - return resp, false, inheritConnection - end - end - - local action, connection = determineActionForURL(url) - - if action == "search" then - return fetchSearch(url, connection), true - elseif action == "internal website" then - return fetchInternal(url), true - elseif action == "external website" then - local resp = fetchExternal(url, connection) - if resp == "retry" then - return fetchError("A connection error/timeout has occurred!"), false, connection - else - return resp, false, connection - end - elseif action == "none" then - return fetchNone(), true - elseif action == "exit" then - os.queueEvent("terminate") - end - - return nil -end - - - --- Tabs - - -local switchTab = function(index, shouldntResume) - if not tabs[index] then - return - end - - if tabs[currentTab].win then - tabs[currentTab].win.setVisible(false) - end - - currentTab = index - isMenubarOpen = tabs[currentTab].isMenubarOpen - currentWebsiteURL = tabs[currentTab].url - - term.redirect(originalTerminal) - clear(theme.background, theme.text) - drawMenubar() - - term.redirect(tabs[currentTab].win) - term.setCursorPos(1, 1) - tabs[currentTab].win.setVisible(true) - tabs[currentTab].win.redraw() - - if not shouldntResume then - coroutine.resume(tabs[currentTab].thread) - end -end - - -local closeCurrentTab = function() - if #tabs <= 0 then - return - end - - table.remove(tabs, currentTab) - - currentTab = math.max(currentTab - 1, 1) - switchTab(currentTab, true) -end - - -local loadTab = function(index, url, givenFunc) - url = normalizeURL(url) - - local func = nil - local isOpen = true - local currentConnection = false - - isMenubarOpen = true - currentWebsiteURL = url - drawMenubar() - - if tabs[index] and tabs[index].connection and tabs[index].url then - if url:match("^([^/]+)") == tabs[index].url:match("^([^/]+)") then - currentConnection = tabs[index].connection - else - tabs[index].connection.close() - tabs[index].connection = nil - end - end - - if givenFunc then - func = givenFunc - else - parallel.waitForAny(function() - func, isOpen, connection = fetchURL(url, currentConnection) - end, function() - while true do - local event, key = os.pullEvent() - if event == "key" and (key == 29 or key == 157) then - break - end - end - end, loadingAnimation) - end - - if func then - appendToHistory(url) - - tabs[index] = {} - tabs[index].url = url - tabs[index].connection = connection - tabs[index].win = window.create(originalTerminal, 1, 1, w, h, false) - - tabs[index].thread = coroutine.create(func) - tabs[index].isMenubarOpen = isOpen - tabs[index].isMenubarPermanent = isOpen - - tabs[index].ox = 1 - tabs[index].oy = 1 - - term.redirect(tabs[index].win) - clear(theme.background, theme.text) - - switchTab(index) - end -end - - - --- Website Environments - - -local getWhitelistedEnvironment = function() - local env = {} - - local function copy(source, destination, key) - destination[key] = {} - for k, v in pairs(source) do - destination[key][k] = v - end - end - - copy(bit, env, "bit") - copy(colors, env, "colors") - copy(colours, env, "colours") - copy(coroutine, env, "coroutine") - - copy(disk, env, "disk") - env["disk"]["setLabel"] = nil - env["disk"]["eject"] = nil - - copy(gps, env, "gps") - copy(help, env, "help") - copy(keys, env, "keys") - copy(math, env, "math") - - copy(os, env, "os") - env["os"]["run"] = nil - env["os"]["shutdown"] = nil - env["os"]["reboot"] = nil - env["os"]["setComputerLabel"] = nil - env["os"]["queueEvent"] = nil - env["os"]["pullEvent"] = function(filter) - while true do - local event = {os.pullEvent(filter)} - if not filter then - return unpack(event) - elseif filter and event[1] == filter then - return unpack(event) - end - end - end - env["os"]["pullEventRaw"] = env["os"]["pullEvent"] - - copy(paintutils, env, "paintutils") - copy(parallel, env, "parallel") - copy(peripheral, env, "peripheral") - copy(rednet, env, "rednet") - copy(redstone, env, "redstone") - copy(redstone, env, "rs") - - copy(shell, env, "shell") - env["shell"]["run"] = nil - env["shell"]["exit"] = nil - env["shell"]["setDir"] = nil - env["shell"]["setAlias"] = nil - env["shell"]["clearAlias"] = nil - env["shell"]["setPath"] = nil - - copy(string, env, "string") - copy(table, env, "table") - - copy(term, env, "term") - env["term"]["redirect"] = nil - env["term"]["restore"] = nil - - copy(textutils, env, "textutils") - copy(vector, env, "vector") - - if turtle then - copy(turtle, env, "turtle") - end - - if http then - copy(http, env, "http") - end - - env["assert"] = assert - env["printError"] = printError - env["tonumber"] = tonumber - env["tostring"] = tostring - env["type"] = type - env["next"] = next - env["unpack"] = unpack - env["pcall"] = pcall - env["xpcall"] = xpcall - env["sleep"] = sleep - env["pairs"] = pairs - env["ipairs"] = ipairs - env["read"] = read - env["write"] = write - env["select"] = select - env["print"] = print - env["setmetatable"] = setmetatable - env["getmetatable"] = getmetatable - - env["_G"] = env - - return env -end - - -local overrideEnvironment = function(env) - local localTerm = {} - for k, v in pairs(term) do - localTerm[k] = v - end - - env["term"]["clear"] = function() - localTerm.clear() - drawMenubar() - end - - env["term"]["scroll"] = function(n) - localTerm.scroll(n) - drawMenubar() - end - - env["shell"]["getRunningProgram"] = function() - return currentWebsiteURL - end -end - -local urlEncode = function(url) - local result = url - - result = result:gsub("%%", "%%a") - result = result:gsub(":", "%%c") - result = result:gsub("/", "%%s") - result = result:gsub("\n", "%%n") - result = result:gsub(" ", "%%w") - result = result:gsub("&", "%%m") - result = result:gsub("%?", "%%q") - result = result:gsub("=", "%%e") - result = result:gsub("%.", "%%d") - - return result -end - -local urlDecode = function(url) - local result = url - - result = result:gsub("%%c", ":") - result = result:gsub("%%s", "/") - result = result:gsub("%%n", "\n") - result = result:gsub("%%w", " ") - result = result:gsub("%%&", "&") - result = result:gsub("%%q", "%?") - result = result:gsub("%%e", "=") - result = result:gsub("%%d", "%.") - result = result:gsub("%%m", "%%") - - return result -end - -local applyAPIFunctions = function(env, connection) - env["firewolf"] = {} - env["firewolf"]["version"] = version - env["firewolf"]["domain"] = currentWebsiteURL:match("^[^/]+") - - env["firewolf"]["redirect"] = function(url) - if type(url) ~= "string" then - return error("string (url) expected, got " .. type(url)) - end - - os.queueEvent(redirectEvent, url) - coroutine.yield() - end - - env["firewolf"]["download"] = function(page) - if type(page) ~= "string" then - return error("string (page) expected") - end - local bannedNames = {"ls", "dir", "delete", "copy", "move", "list", "rm", "cp", "mv", "clear", "cd", "lua"} - - local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+")) - if startSearch == 1 then - if page:sub(endSearch + 1, endSearch + 1) == "/" then - page = page:sub(endSearch + 2, -1) - else - page = page:sub(endSearch + 1, -1) - end - end - - local filename = page:match("([^/]+)$") - if not filename then - return false, "Cannot download index" - end - - for k, v in pairs(bannedNames) do - if filename == v then - return false, "Filename prohibited!" - end - end - - if not fs.exists(downloadsLocation) then - fs.makeDir(downloadsLocation) - elseif not fs.isDir(downloadsLocation) then - return false, "Downloads disabled!" - end - - contents = connection.fetchPage(normalizePage(page)) - if type(contents) ~= "string" then - return false, "Download error!" - else - local f = io.open(downloadsLocation .. "/" .. filename, "w") - f:write(contents) - f:close() - return true, downloadsLocation .. "/" .. filename - end - end - - env["firewolf"]["encode"] = function(vars) - if type(vars) ~= "table" then - return error("table (vars) expected, got " .. type(vars)) - end - - local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+")) - if startSearch == 1 then - if page:sub(endSearch + 1, endSearch + 1) == "/" then - page = page:sub(endSearch + 2, -1) - else - page = page:sub(endSearch + 1, -1) - end - end - - local construct = "?" - for k,v in pairs(vars) do - construct = construct .. urlEncode(tostring(k)) .. "=" .. urlEncode(tostring(v)) .. "&" - end - -- Get rid of that last ampersand - construct = construct:sub(1, -2) - - return construct - end - - env["firewolf"]["query"] = function(page, vars) - if type(page) ~= "string" then - return error("string (page) expected, got " .. type(page)) - end - if vars and type(vars) ~= "table" then - return error("table (vars) expected, got " .. type(vars)) - end - - local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+")) - if startSearch == 1 then - if page:sub(endSearch + 1, endSearch + 1) == "/" then - page = page:sub(endSearch + 2, -1) - else - page = page:sub(endSearch + 1, -1) - end - end - - local construct = page .. "?" - if vars then - for k,v in pairs(vars) do - construct = construct .. urlEncode(tostring(k)) .. "=" .. urlEncode(tostring(v)) .. "&" - end - end - -- Get rid of that last ampersand - construct = construct:sub(1, -2) - - contents = connection.fetchPage(normalizePage(construct)) - if type(contents) == "string" then - return contents - else - return false - end - end - - env["firewolf"]["loadImage"] = function(page) - if type(page) ~= "string" then - return error("string (page) expected, got " .. type(page)) - end - - local startSearch, endSearch = page:find(currentWebsiteURL:match("^[^/]+")) - if startSearch == 1 then - if page:sub(endSearch + 1, endSearch + 1) == "/" then - page = page:sub(endSearch + 2, -1) - else - page = page:sub(endSearch + 1, -1) - end - end - - local filename = page:match("([^/]+)$") - if not filename then - return false, "Cannot load index as an image!" - end - - contents = connection.fetchPage(normalizePage(page)) - if type(contents) ~= "string" then - return false, "Download error!" - else - local colorLookup = {} - for n = 1, 16 do - colorLookup[string.byte("0123456789abcdef", n, n)] = 2 ^ (n - 1) - end - - local image = {} - for line in contents:gmatch("[^\n]+") do - local lines = {} - for x = 1, line:len() do - lines[x] = colorLookup[string.byte(line, x, x)] or 0 - end - table.insert(image, lines) - end - - return image - end - end - - env["center"] = center - env["fill"] = fill -end - - -local getWebsiteEnvironment = function(antivirus, connection) - local env = {} - - if antivirus then - env = getWhitelistedEnvironment() - overrideEnvironment(env) - else - setmetatable(env, {__index = _G}) - end - - applyAPIFunctions(env, connection) - - return env -end - - - --- FWML Execution - - -local render = {} - -render["functions"] = {} -render["functions"]["public"] = {} -render["alignations"] = {} - -render["variables"] = { - scroll, - maxScroll, - align, - linkData = {}, - blockLength, - link, - linkStart, - markers, - currentOffset, -} - - -local function getLine(loc, data) - local _, changes = data:sub(1, loc):gsub("\n", "") - if not changes then - return 1 - else - return changes + 1 - end -end - - -local function parseData(data) - local commands = {} - local searchPos = 1 - - while #data > 0 do - local sCmd, eCmd = data:find("%[[^%]]+%]", searchPos) - if sCmd then - sCmd = sCmd + 1 - eCmd = eCmd - 1 - - if (sCmd > 2) then - if data:sub(sCmd - 2, sCmd - 2) == "\\" then - local t = data:sub(searchPos, sCmd - 1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]") - if #t > 0 then - if #commands > 0 and type(commands[#commands][1]) == "string" then - commands[#commands][1] = commands[#commands][1] .. t - else - table.insert(commands, {t}) - end - end - searchPos = sCmd - else - local t = data:sub(searchPos, sCmd - 2):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]") - if #t > 0 then - if #commands > 0 and type(commands[#commands][1]) == "string" then - commands[#commands][1] = commands[#commands][1] .. t - else - table.insert(commands, {t}) - end - end - - t = data:sub(sCmd, eCmd):gsub("\n", "") - table.insert(commands, {getLine(sCmd, data), t}) - searchPos = eCmd + 2 - end - else - local t = data:sub(sCmd, eCmd):gsub("\n", "") - table.insert(commands, {getLine(sCmd, data), t}) - searchPos = eCmd + 2 - end - else - local t = data:sub(searchPos, -1):gsub("\n", ""):gsub("\\%[", "%["):gsub("\\%]", "%]") - if #t > 0 then - if #commands > 0 and type(commands[#commands][1]) == "string" then - commands[#commands][1] = commands[#commands][1] .. t - else - table.insert(commands, {t}) - end - end - - break - end - end - - return commands -end - - -local function proccessData(commands) - searchIndex = 0 - - while searchIndex < #commands do - searchIndex = searchIndex + 1 - - local length = 0 - local origin = searchIndex - - if type(commands[searchIndex][1]) == "string" then - length = length + #commands[searchIndex][1] - local endIndex = origin - for i = origin + 1, #commands do - if commands[i][2] then - local command = commands[i][2]:match("^(%w+)%s-") - if not (command == "c" or command == "color" or command == "bg" - or command == "background" or command == "newlink" or command == "endlink") then - endIndex = i - break - end - elseif commands[i][2] then - - else - length = length + #commands[i][1] - end - if i == #commands then - endIndex = i - end - end - - commands[origin][2] = length - searchIndex = endIndex - length = 0 - end - end - - return commands -end - - -local function parse(original) - return proccessData(parseData(original)) -end - - -render["functions"]["display"] = function(text, length, offset, center) - if not offset then - offset = 0 - end - - return render.variables.align(text, length, w, offset, center); -end - - -render["functions"]["displayText"] = function(source) - if source[2] then - render.variables.blockLength = source[2] - if render.variables.link and not render.variables.linkStart then - render.variables.linkStart = render.functions.display( - source[1], render.variables.blockLength, render.variables.currentOffset, w / 2) - else - render.functions.display(source[1], render.variables.blockLength, render.variables.currentOffset, w / 2) - end - else - if render.variables.link and not render.variables.linkStart then - render.variables.linkStart = render.functions.display(source[1], nil, render.variables.currentOffset, w / 2) - else - render.functions.display(source[1], nil, render.variables.currentOffset, w / 2) - end - end -end - - -render["functions"]["public"]["br"] = function(source) - if render.variables.link then - return "Cannot insert new line within a link on line " .. source[1] - end - - render.variables.scroll = render.variables.scroll + 1 - render.variables.maxScroll = math.max(render.variables.scroll, render.variables.maxScroll) -end - - -render["functions"]["public"]["c "] = function(source) - local sColor = source[2]:match("^%w+%s+(.+)$") or "" - if colors[sColor] then - term.setTextColor(colors[sColor]) - else - return "Invalid color: \"" .. sColor .. "\" on line " .. source[1] - end -end - - -render["functions"]["public"]["color "] = render["functions"]["public"]["c "] - - -render["functions"]["public"]["bg "] = function(source) - local sColor = source[2]:match("^%w+%s+(.+)$") or "" - if colors[sColor] then - term.setBackgroundColor(colors[sColor]) - else - return "Invalid color: \"" .. sColor .. "\" on line " .. source[1] - end -end - - -render["functions"]["public"]["background "] = render["functions"]["public"]["bg "] - - -render["functions"]["public"]["newlink "] = function(source) - if render.variables.link then - return "Cannot nest links on line " .. source[1] - end - - render.variables.link = source[2]:match("^%w+%s+(.+)$") or "" - render.variables.linkStart = false -end - - -render["functions"]["public"]["endlink"] = function(source) - if not render.variables.link then - return "Cannot end a link without a link on line " .. source[1] - end - - local linkEnd = term.getCursorPos()-1 - table.insert(render.variables.linkData, {render.variables.linkStart, - linkEnd, render.variables.scroll, render.variables.link}) - render.variables.link = false - render.variables.linkStart = false -end - - -render["functions"]["public"]["offset "] = function(source) - local offset = tonumber((source[2]:match("^%w+%s+(.+)$") or "")) - if offset then - render.variables.currentOffset = offset - else - return "Invalid offset value: \"" .. (source[2]:match("^%w+%s+(.+)$") or "") .. "\" on line " .. source[1] - end -end - - -render["functions"]["public"]["marker "] = function(source) - render.variables.markers[(source[2]:match("^%w+%s+(.+)$") or "")] = render.variables.scroll -end - - -render["functions"]["public"]["goto "] = function(source) - local location = source[2]:match("%w+%s+(.+)$") - if render.variables.markers[location] then - render.variables.scroll = render.variables.markers[location] - else - return "No such location: \"" .. (source[2]:match("%w+%s+(.+)$") or "") .. "\" on line " .. source[1] - end -end - - -render["functions"]["public"]["box "] = function(source) - local sColor, align, height, width, offset, url = source[2]:match("^box (%a+) (%a+) (%-?%d+) (%-?%d+) (%-?%d+) ?([^ ]*)") - if not sColor then - return "Invalid box syntax on line " .. source[1] - end - - local x, y = term.getCursorPos() - local startX - - if align == "center" or align == "centre" then - startX = math.ceil((w / 2) - width / 2) + offset - elseif align == "left" then - startX = 1 + offset - elseif align == "right" then - startX = (w - width + 1) + offset - else - return "Invalid align option for box on line " .. source[1] - end - - if not colors[sColor] then - return "Invalid color: \"" .. sColor .. "\" for box on line " .. source[1] - end - - term.setBackgroundColor(colors[sColor]) - for i = 0, height - 1 do - term.setCursorPos(startX, render.variables.scroll + i) - term.write(string.rep(" ", width)) - if url:len() > 3 then - table.insert(render.variables.linkData, {startX, startX + width - 1, render.variables.scroll + i, url}) - end - end - - render.variables.maxScroll = math.max(render.variables.scroll + height - 1, render.variables.maxScroll) - term.setCursorPos(x, y) -end - - -render["alignations"]["left"] = function(text, length, _, offset) - local x, y = term.getCursorPos() - if length then - term.setCursorPos(1 + offset, render.variables.scroll) - term.write(text) - return 1 + offset - else - term.setCursorPos(x, render.variables.scroll) - term.write(text) - return x - end -end - - -render["alignations"]["right"] = function(text, length, width, offset) - local x, y = term.getCursorPos() - if length then - term.setCursorPos((width - length + 1) + offset, render.variables.scroll) - term.write(text) - return (width - length + 1) + offset - else - term.setCursorPos(x, render.variables.scroll) - term.write(text) - return x - end -end - - -render["alignations"]["center"] = function(text, length, _, offset, center) - local x, y = term.getCursorPos() - if length then - term.setCursorPos(math.ceil(center - length / 2) + offset, render.variables.scroll) - term.write(text) - return math.ceil(center - length / 2) + offset - else - term.setCursorPos(x, render.variables.scroll) - term.write(text) - return x - end -end - - -render["render"] = function(data, startScroll) - if startScroll == nil then - render.variables.startScroll = 0 - else - render.variables.startScroll = startScroll - end - - render.variables.scroll = startScroll + 1 - render.variables.maxScroll = render.variables.scroll - - render.variables.linkData = {} - - render.variables.align = render.alignations.left - - render.variables.blockLength = 0 - render.variables.link = false - render.variables.linkStart = false - render.variables.markers = {} - render.variables.currentOffset = 0 - - for k, v in pairs(data) do - if type(v[2]) ~= "string" then - render.functions.displayText(v) - elseif v[2] == "<" or v[2] == "left" then - render.variables.align = render.alignations.left - elseif v[2] == ">" or v[2] == "right" then - render.variables.align = render.alignations.right - elseif v[2] == "=" or v[2] == "center" then - render.variables.align = render.alignations.center - else - local existentFunction = false - - for name, func in pairs(render.functions.public) do - if v[2]:find(name) == 1 then - existentFunction = true - local ret = func(v) - if ret then - return ret - end - end - end - - if not existentFunction then - return "Non-existent tag: \"" .. v[2] .. "\" on line " .. v[1] - end - end - end - - return render.variables.linkData, render.variables.maxScroll - render.variables.startScroll -end - - - --- Lua Execution - - -languages["lua"] = {} -languages["fwml"] = {} - - -languages["lua"]["runWithErrorCatching"] = function(func, ...) - local _, err = pcall(func, ...) - if err then - os.queueEvent(websiteErrorEvent, err) - end -end - - -languages["lua"]["runWithoutAntivirus"] = function(func, ...) - local args = {...} - local env = getWebsiteEnvironment(false) - setfenv(func, env) - return function() - languages["lua"]["runWithErrorCatching"](func, unpack(args)) - end -end - - -languages["lua"]["run"] = function(contents, page, connection, ...) - local func, err = loadstring("sleep(0) " .. contents, page) - if err then - return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], err) - else - local args = {...} - local env = getWebsiteEnvironment(true, connection) - setfenv(func, env) - return function() - languages["lua"]["runWithErrorCatching"](func, unpack(args)) - end - end -end - - -languages["fwml"]["run"] = function(contents, page, connection, ...) - local err, data = pcall(parse, contents) - if not err then - return languages["lua"]["runWithoutAntivirus"](builtInSites["crash"], data) - end - - return function() - local currentScroll = 0 - local err, links, pageHeight = pcall(render.render, data, currentScroll) - if type(links) == "string" or not err then - term.clear() - os.queueEvent(websiteErrorEvent, links) - else - while true do - local e, scroll, x, y = os.pullEvent() - if e == "mouse_click" then - for k, v in pairs(links) do - if x >= math.min(v[1], v[2]) and x <= math.max(v[1], v[2]) and y == v[3] then - os.queueEvent(redirectEvent, v[4]) - coroutine.yield() - end - end - elseif e == "mouse_scroll" then - if currentScroll - scroll - h >= -pageHeight and currentScroll - scroll <= 0 then - currentScroll = currentScroll - scroll - clear(theme.background, theme.text) - links = render.render(data, currentScroll) - end - elseif e == "key" and scroll == keys.up or scroll == keys.down then - local scrollAmount - - if scroll == keys.up then - scrollAmount = 1 - elseif scroll == keys.down then - scrollAmount = -1 - end - - local scrollLessHeight = currentScroll + scrollAmount - h >= -pageHeight - local scrollZero = currentScroll + scrollAmount <= 0 - if scrollLessHeight and scrollZero then - currentScroll = currentScroll + scrollAmount - clear(theme.background, theme.text) - links = render.render(data, currentScroll) - end - end - end - end - end -end - - - --- Query Bar - - -local readNewWebsiteURL = function() - local onEvent = function(text, event, key, x, y) - if event == "mouse_click" then - if y == 2 then - local index = determineClickedTab(x, y) - if index == "new" and #tabs < maxTabs then - loadTab(#tabs + 1, "firewolf") - elseif index == "close" then - closeCurrentTab() - elseif index then - switchTab(index) - end - - return {["nullifyText"] = true, ["exit"] = true} - elseif y > 2 then - return {["nullifyText"] = true, ["exit"] = true} - end - elseif event == "key" then - if key == 29 or key == 157 then - return {["nullifyText"] = true, ["exit"] = true} - end - end - end - - isMenubarOpen = true - drawMenubar() - term.setCursorPos(2, 1) - term.setTextColor(theme.text) - term.setBackgroundColor(theme.accent) - term.clearLine() - term.write(currentProtocol .. "://") - - local website = modifiedRead({ - ["onEvent"] = onEvent, - ["displayLength"] = w - 9, - ["history"] = history, - }) - - if not website then - if not tabs[currentTab].isMenubarPermanent then - isMenubarOpen = false - menubarWindow.setVisible(false) - else - isMenubarOpen = true - menubarWindow.setVisible(true) - end - - term.redirect(tabs[currentTab].win) - tabs[currentTab].win.setVisible(true) - tabs[currentTab].win.redraw() - - return - elseif website == "exit" then - error() - end - - loadTab(currentTab, website) -end - - - --- Event Management - - -local handleKeyDown = function(event) - if event[2] == 29 or event[2] == 157 then - readNewWebsiteURL() - return true - end - - return false -end - - -local handleMouseDown = function(event) - if isMenubarOpen then - if event[4] == 1 then - readNewWebsiteURL() - return true - elseif event[4] == 2 then - local index = determineClickedTab(event[3], event[4]) - if index == "new" and #tabs < maxTabs then - loadTab(#tabs + 1, "firewolf") - elseif index == "close" then - closeCurrentTab() - elseif index then - switchTab(index) - end - - return true - end - end - - return false -end - - -local handleEvents = function() - loadTab(1, "firewolf") - currentTab = 1 - - while true do - drawMenubar() - local event = {os.pullEventRaw()} - drawMenubar() - - local cancelEvent = false - if event[1] == "terminate" then - break - elseif event[1] == "key" then - cancelEvent = handleKeyDown(event) - elseif event[1] == "mouse_click" then - cancelEvent = handleMouseDown(event) - elseif event[1] == websiteErrorEvent then - cancelEvent = true - - loadTab(currentTab, tabs[currentTab].url, function() - builtInSites["crash"](event[2]) - end) - elseif event[1] == redirectEvent then - cancelEvent = true - - if (event[2]:match("^rdnt://(.+)$")) then - event[2] = event[2]:match("^rdnt://(.+)$") - end - - loadTab(currentTab, event[2]) - end - - if not cancelEvent then - term.redirect(tabs[currentTab].win) - term.setCursorPos(tabs[currentTab].ox, tabs[currentTab].oy) - - coroutine.resume(tabs[currentTab].thread, unpack(event)) - - local ox, oy = term.getCursorPos() - tabs[currentTab].ox = ox - tabs[currentTab].oy = oy - end - end -end - - - --- Main - - -local main = function() - currentProtocol = "rdnt" - currentTab = 1 - - if term.isColor() then - theme = colorTheme - enableTabBar = true - else - theme = grayscaleTheme - enableTabBar = false - end - - setupMenubar() - protocols[currentProtocol]["setup"]() - - clear(theme.background, theme.text) - handleEvents() -end - - -local handleError = function(err) - clear(theme.background, theme.text) - - fill(1, 3, w, 3, theme.subtle) - term.setCursorPos(1, 4) - center("Firewolf has crashed!") - - term.setBackgroundColor(theme.background) - term.setCursorPos(1, 8) - centerSplit(err, w - 4) - print("\n") - center("Please report this error to") - center("GravityScore or 1lann.") - print("") - center("Press any key to exit.") - - os.pullEvent("key") - os.queueEvent("") - os.pullEvent() -end - -local _, err = pcall(main) -term.redirect(originalTerminal) - -Modem.closeAll() - -if err and not err:lower():find("terminate") then - handleError(err) -end - - -clear(colors.black, colors.white) -center("Thanks for using Firewolf " .. version) -center("Made by GravityScore and 1lann") -print("") \ No newline at end of file -- cgit v1.2.3