Guest User

Untitled

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