diff --git a/README.md b/README.md index e0fe3c2c..519ca4af 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,30 @@ ![](https://reposs.herokuapp.com/?path=gchq/CyberChef&color=blue) [![](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/gchq/CyberChef/blob/master/LICENSE) [![Gitter](https://badges.gitter.im/gchq/CyberChef.svg)](https://gitter.im/gchq/CyberChef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +This is a fork of original CyberChef by GCHQ. This project has implemented my own operations for the daily SoC tasks. Please feel free to clone/test/share/contribute it. +## Features + +- opeartion **nTcpdump**: tcpdump hexdump convert +- operation **From 0x[Hex]**: e.g. 0x217e21 to !~! +- operation **From char(hex)**: + - e.g. chr(33) to ! + - This operation supports char() and chr() + - Combining the usage of **From 0x[Hex]** and **From char(hex)** can decode chr(0x3333) to ! +- operation **HTTP gzip decrypt**: Decrypt gzip payload in HTTP + +## Todo + +- Operation SQL comment strip function +- Adding more support to char(hex, hex) +- Adding support of themes + + +--- + +## Original project info + +####*The Cyber Swiss Army Knife* #### *The Cyber Swiss Army Knife* diff --git a/prod_image_list.txt b/prod_image_list.txt new file mode 100644 index 00000000..e3ab8d31 --- /dev/null +++ b/prod_image_list.txt @@ -0,0 +1,26 @@ +./src/web/static/images/bug-16x16.png +./src/web/static/images/clean-16x16.png +./src/web/static/images/cook_male-32x32.png +./src/web/static/images/cyberchef-128x128.png +./src/web/static/images/download-24x24.png +./src/web/static/images/erase-16x16.png +./src/web/static/images/favourite-16x16.png +./src/web/static/images/favourite-24x24.png +./src/web/static/images/fork_me.png +./src/web/static/images/gitter-badge.svg +./src/web/static/images/help-16x16.png +./src/web/static/images/help-22x22.png +./src/web/static/images/layout-16x16.png +./src/web/static/images/maximise-16x16.png +./src/web/static/images/open_yellow-16x16.png +./src/web/static/images/open_yellow-24x24.png +./src/web/static/images/recycle-16x16.png +./src/web/static/images/save-22x22.png +./src/web/static/images/save_as-16x16.png +./src/web/static/images/settings-22x22.png +./src/web/static/images/speech-16x16.png +./src/web/static/images/step-16x16.png +./src/web/static/images/switch-16x16.png +./src/web/static/images/thumb_down-16x16.png +./src/web/static/images/thumb_up-16x16.png +./src/web/static/images/undo-16x16.png \ No newline at end of file diff --git a/sample_recipe.md b/sample_recipe.md new file mode 100644 index 00000000..f0583bdd --- /dev/null +++ b/sample_recipe.md @@ -0,0 +1,16 @@ +# Ready-To-Go Sample Recipe + +## tcpdump (no ASCII) Daily Use + +1. tcpdump (no ASCII) convert to plaintext (e.g. 0x0000: 0020 0A0D) +2. URL decode loop (e.g. %3d to =) +3. From HTML Entity (e.g. & to &) +4. From 0x[Hex] (e.g. 0x33 to !) +5. From Char(Hex) (e.g. char(33) to !) + + [{"op":"From nTcpdump","args":[]}, + {"op":"URL Decode","args":[]}, + {"op":"Conditional Jump","args":["\\%([0-9a-fA-F]{2,})","-1","45"]}, + {"op":"From HTML Entity","args":[]}, + {"op":"From 0x[Hex]","args":[]}, + {"op":"From Char(Hex)","args":[]}] \ No newline at end of file diff --git a/src/core/Utils.js b/src/core/Utils.js index e7243201..1d5c1654 100755 --- a/src/core/Utils.js +++ b/src/core/Utils.js @@ -340,6 +340,68 @@ const Utils = { }, + /** + * Translates a hex string into an array of bytes. + * + * @param {string} hexStr + * @returns {byteArray} + * + * @example + * // returns [0xfe, 0x09, 0xa7] + * Utils.hexToByteArray("fe09a7"); + */ + hexToByteArray: function(hexStr) { + // TODO: Handle errors i.e. input string is not hex + if (!hexStr) return []; + hexStr = hexStr.replace(/\s+/g, ""); + const byteArray = []; + for (let i = 0; i < hexStr.length; i += 2) { + byteArray.push(parseInt(hexStr.substr(i, 2), 16)); + } + return byteArray; + }, + + + /** + * Translates an array of bytes to a hex string. + * + * @param {byteArray} byteArray + * @returns {string} + * + * @example + * // returns "fe09a7" + * Utils.byteArrayToHex([0xfe, 0x09, 0xa7]); + */ + byteArrayToHex: function(byteArray) { + if (!byteArray) return ""; + let hexStr = ""; + for (let i = 0; i < byteArray.length; i++) { + hexStr += Utils.hex(byteArray[i]) + " "; + } + return hexStr.slice(0, hexStr.length-1); + }, + + + /** + * Translates an array of bytes to a hex string. + * + * @param {byteArray} byteArray + * @returns {string} + * + * @example + * // returns "fe09a7" + * Utils.byteArrayToHex([0xfe, 0x09, 0xa7]); + */ + byteArrayToHexNoSpace: function(byteArray) { + if (!byteArray) return ""; + let hexStr = ""; + for (let i = 0; i < byteArray.length; i++) { + hexStr += Utils.hex(byteArray[i]); + } + return hexStr.slice(0, hexStr.length-1); + }, + + /** * Converts a string to a byte array. * Treats the string as UTF-8 if any values are over 255. diff --git a/src/core/config/Categories.js b/src/core/config/Categories.js index e2ee57cf..f05885b3 100755 --- a/src/core/config/Categories.js +++ b/src/core/config/Categories.js @@ -27,6 +27,8 @@ const Categories = [ ops: [ "To Hexdump", "From Hexdump", + "From 0x[Hex]", + "From Char(Hex)", "To Hex", "From Hex", "To Charcode", @@ -304,6 +306,14 @@ const Categories = [ "To Kebab case", ] }, + { + name: "Packets", + ops: [ + "From Tcpdump", + "HTTP gzip Decrypt", + "Strip TCP Headers", + ] + }, { name: "Other", ops: [ diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 2f1d6781..ae1a7c28 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -37,7 +37,7 @@ import StrUtils from "../operations/StrUtils.js"; import Tidy from "../operations/Tidy.js"; import Unicode from "../operations/Unicode.js"; import URL_ from "../operations/URL.js"; - +import Packets from "../operations/Packets.js"; /** * Type definition for an OpConf. @@ -520,6 +520,24 @@ const OperationConfig = { } ] }, + "From 0x[Hex]": { + module: "Default", + description: "Converts a hexadecimal byte string back into a its raw value.

e.g. 0x217e21 becomes the UTF-8 encoded string !~!", + highlight: ByteRepr.highlightFrom, + highlightReverse: ByteRepr.highlightTo, + inputType: "string", + outputType: "string", + args: [] + }, + "From Char(Hex)": { + module: "Default", + description: "Converts a hexadecimal byte string back into a its raw value.

e.g. chr(33) becomes the UTF-8 encoded string !", + highlight: ByteRepr.highlightFrom, + highlightReverse: ByteRepr.highlightTo, + inputType: "string", + outputType: "string", + args: [] + }, "Sum": { module: "Default", description: "Adds together a list of numbers. If an item in the string is not a number it is excluded from the list.

e.g. 0x0a 8 .5 becomes 18.5", @@ -1851,6 +1869,24 @@ const OperationConfig = { outputType: "string", args: [] }, + "HTTP gzip Decrypt": { + module: "Compression", + description: "Decrypts Gzip payload from a request or response and returning plaintext of the header and decrypted payload.

Arguments:
Library: The library used for decoding GZIP data.
Threshold: The threshold value for searching non-GZIP data. It has to be at least 8.", + inputType: "byteArray", + outputType: "byteArray", + args: [ + { + name: "Library", + type: "option", + value: Compress.HTTP_GZIP_OPTION + }, + { + name: "Threshold", + type: "number", + value: Compress.HTTP_GZIP_THRESHOLD + }, + ] + }, "Parse User Agent": { module: "HTTP", description: "Attempts to identify and categorise information contained in a user-agent string.", @@ -3964,6 +4000,36 @@ const OperationConfig = { } ] }, + "From Tcpdump": { + module: "Packets", + description: "[DEPRECATED] Converts Tcpdump hex to string", + inputType: "string", + outputType: "byteArray", + args: [] + }, + "Strip TCP Headers": { + module: "Packets", + description: "Remove selected TCP headers from hexstream using Regular Expressions.

Ethernet Header: /^(([0-9a-f]{4} ){6,8}0800 )/igm
IP Header: /^((45[0-9a-f]{2} ([0-9a-f]{4} ){9}))/igm
TCP Header: /^([0-9a-f]{4} ){6}((80[0-9a-f]{2} ([0-9a-f]{4} ?){9})|(50[0-9a-f]{2} ([0-9a-f]{4} ?){3}))/igm", + inputType: "string", + outputType: "string", + args: [ + { + name: "Ethernet Header", + type: "boolean", + value: Packets.STRIP_ETHERNET_HEADER, + }, + { + name: "IP Header", + type: "boolean", + value: Packets.STRIP_IP_HEADER, + }, + { + name: "Ethernet Header", + type: "boolean", + value: Packets.STRIP_TCP_HEADER, + }, + ] + }, "PHP Deserialize": { module: "Default", description: "Deserializes PHP serialized data, outputting keyed arrays as JSON.

This function does not support object tags.

Example:
a:2:{s:1:"a";i:10;i:0;a:1:{s:2:"ab";b:1;}}
becomes
{"a": 10,0: {"ab": true}}

Output valid JSON: JSON doesn't support integers as keys, whereas PHP serialization does. Enabling this will cast these integers to strings. This will also escape backslashes.", diff --git a/src/core/config/modules/Compression.js b/src/core/config/modules/Compression.js index aa8d074f..9d55b400 100644 --- a/src/core/config/modules/Compression.js +++ b/src/core/config/modules/Compression.js @@ -15,17 +15,18 @@ import Compress from "../../operations/Compress.js"; let OpModules = typeof self === "undefined" ? {} : self.OpModules || {}; OpModules.Compression = { - "Raw Deflate": Compress.runRawDeflate, - "Raw Inflate": Compress.runRawInflate, - "Zlib Deflate": Compress.runZlibDeflate, - "Zlib Inflate": Compress.runZlibInflate, - "Gzip": Compress.runGzip, - "Gunzip": Compress.runGunzip, - "Zip": Compress.runPkzip, - "Unzip": Compress.runPkunzip, - "Bzip2 Decompress": Compress.runBzip2Decompress, - "Tar": Compress.runTar, - "Untar": Compress.runUntar, + "Raw Deflate": Compress.runRawDeflate, + "Raw Inflate": Compress.runRawInflate, + "Zlib Deflate": Compress.runZlibDeflate, + "Zlib Inflate": Compress.runZlibInflate, + "Gzip": Compress.runGzip, + "Gunzip": Compress.runGunzip, + "Zip": Compress.runPkzip, + "Unzip": Compress.runPkunzip, + "Bzip2 Decompress": Compress.runBzip2Decompress, + "Tar": Compress.runTar, + "Untar": Compress.runUntar, + "HTTP gzip Decrypt": Compress.runHttpGzip, }; diff --git a/src/core/config/modules/Default.js b/src/core/config/modules/Default.js index 4fb76887..44c4d9c9 100644 --- a/src/core/config/modules/Default.js +++ b/src/core/config/modules/Default.js @@ -53,6 +53,8 @@ OpModules.Default = { "From Hexdump": Hexdump.runFrom, "To Hex": ByteRepr.runToHex, "From Hex": ByteRepr.runFromHex, + "From 0x[Hex]": ByteRepr.runFrom0xHex, + "From Char(Hex)": ByteRepr.runFromCharHex, "To Octal": ByteRepr.runToOct, "From Octal": ByteRepr.runFromOct, "To Charcode": ByteRepr.runToCharcode, diff --git a/src/core/config/modules/OpModules.js b/src/core/config/modules/OpModules.js index 3f3963c3..b7a9ca2b 100644 --- a/src/core/config/modules/OpModules.js +++ b/src/core/config/modules/OpModules.js @@ -20,6 +20,7 @@ import JSBNModule from "./JSBN.js"; import PublicKeyModule from "./PublicKey.js"; import ShellcodeModule from "./Shellcode.js"; import URLModule from "./URL.js"; +import PacketsModule from "./Packets.js"; Object.assign( OpModules, @@ -35,7 +36,8 @@ Object.assign( JSBNModule, PublicKeyModule, ShellcodeModule, - URLModule + URLModule, + PacketsModule ); export default OpModules; diff --git a/src/core/config/modules/Packets.js b/src/core/config/modules/Packets.js new file mode 100644 index 00000000..1d441ca4 --- /dev/null +++ b/src/core/config/modules/Packets.js @@ -0,0 +1,20 @@ +import Packets from "../../operations/Packets.js"; + +/** + * Packets module. + * + * Libraries: + * - Utils.js + * + * @author drkna [whytho@email] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ +let OpModules = typeof self === "undefined" ? {} : self.OpModules || {}; + +OpModules.Packets = { + "From Tcpdump": Packets.runFromTcpdump, + "Strip TCP Headers": Packets.stripPacketHeader +}; + +export default OpModules; diff --git a/src/core/operations/ByteRepr.js b/src/core/operations/ByteRepr.js index 87a543ff..3183a7c9 100755 --- a/src/core/operations/ByteRepr.js +++ b/src/core/operations/ByteRepr.js @@ -54,6 +54,48 @@ const ByteRepr = { }, + /** + * From 0xHex operation. + * + * @param {string} input (Starting with 0x only in the raw input) + * @param {Object[]} args + * @returns {byteArray} + */ + runFrom0xHex: function(input, args) { + let data = input.replace(/0x([0-9a-f]{2,})/ig, + function(match, p1) { + if (p1) { + return Utils.byteArrayToChars(Utils.fromHex(p1)); + } + }); + return data; + }, + + + /** + * From char(hex) operation. + * + * @param {string} input (Starting with chr or char only in the raw input) + * @param {Object[]} args + * @returns {byteArray} + */ + runFromCharHex: function(input, args) { + let re = /cha?r\((((\d{1,3})(,\s?)?)+)\)/ig; + let innerRe = /(\d{1,3}),?/g; + let data = input.replace(re, + function(match, p1) { + if (p1) { + let result = "", innerMatch; + while ((innerMatch = innerRe.exec(p1)) != null) { + result += Utils.byteArrayToChars([parseInt(innerMatch[1], 10)]); + } + return result; + } + }); + return data; + }, + + /** * To Octal operation. * diff --git a/src/core/operations/Compress.js b/src/core/operations/Compress.js index 639b89c6..7748f9d9 100755 --- a/src/core/operations/Compress.js +++ b/src/core/operations/Compress.js @@ -5,6 +5,7 @@ import zlibAndGzip from "zlibjs/bin/zlib_and_gzip.min"; import zip from "zlibjs/bin/zip.min"; import unzip from "zlibjs/bin/unzip.min"; import bzip2 from "exports-loader?bzip2!../lib/bzip2.js"; +import pako from "pako/index.js"; const Zlib = { RawDeflate: rawdeflate.Zlib.RawDeflate, @@ -254,6 +255,69 @@ const Compress = { }, + /** + * @constant + * @default + */ + HTTP_GZIP_OPTION: ["pako.js", "zlib.js"], + /** + * @constant + * @default + */ + HTTP_GZIP_THRESHOLD: 8, + + /** + * HTTP Gzip operation. + * + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + runHttpGzip: function(input, args) { + const library = Compress.HTTP_GZIP_OPTION.indexOf(args[0]); + let threshold = Compress.HTTP_GZIP_THRESHOLD; + if (args[1] > 8) { + threshold = args[1]; + } + input = Utils.byteArrayToHexNoSpace(input); + let output = input; + + let regexStr = /1f8b080[0-8][0-9a-f]{12}/; + let gzipPos = input.search(regexStr); + if (gzipPos === -1) { + return Utils.hexToByteArray(input); + } + + while (gzipPos !== -1) { + output = input; + + let plainData = output.substr(0, gzipPos); + let gzipData = output.substr(gzipPos); + let httpDataAfter = ""; + + let httpDataPosRegex = new RegExp("/((3[0-9])|(6[0-9a-f])|(7[0-9a])|(4[0-9a-f])|(5[0-9a])|(2[e-f])|(2b)|(20)){" + threshold + "}/"); + let httpDataPos = gzipData.search(httpDataPosRegex); + if (httpDataPos !== -1) { + httpDataAfter = gzipData.substr(httpDataPos); + gzipData = gzipData.substr(0, httpDataPos); + } + + console.log(httpDataPos); + gzipData = Utils.hexToByteArray(gzipData); + if (library === 0) { + output = Utils.hexToByteArray(plainData).concat(Array.prototype.slice.call(pako.inflate(gzipData))).concat(Utils.hexToByteArray(httpDataAfter)); + } else if (library === 1) { + let gzipDataRaw = new Zlib.Gunzip(gzipData); + output = Utils.hexToByteArray(plainData).concat(Array.prototype.slice.call(gzipDataRaw.decompress())).concat(Utils.hexToByteArray("0d0a 0d0a")).concat(Utils.hexToByteArray(httpDataAfter)); + } + + input = Utils.byteArrayToHexNoSpace(output); + gzipPos = input.search(regexStr); + } + return output; + }, + + /** * @constant * @default diff --git a/src/core/operations/Packets.js b/src/core/operations/Packets.js new file mode 100644 index 00000000..b5825003 --- /dev/null +++ b/src/core/operations/Packets.js @@ -0,0 +1,81 @@ +import Utils from "../Utils.js"; + + +/** + * Packets operations. + * + * @author drkna [whytho@email] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @namespace + */ +const Packets = { + /** + * From Tcpdump Hexstring operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + runFromTcpdump: function(input, args) { + let output = []; + let regex = /^\s*(?:0x[\dA-F]{4}:?)?\s*((?:[\dA-F]{4}\s?){1,8})/igm; + let block = regex.exec(input); + while (block) { + let line = Utils.fromHex(block[1].replace(/-/g, " ")); + for (let i = 0; i < line.length; i++) { + output.push(line[i]); + } + block = regex.exec(input); + } + return output; + }, + + + /** + * @constant + * @default + */ + STRIP_ETHERNET_HEADER: true, + + /** + * @constant + * @default + */ + STRIP_IP_HEADER: true, + + /** + * @constant + * @default + */ + STRIP_TCP_HEADER: true, + + /** + * Strip TCP Headersoperation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + stripPacketHeader: function(input, args) { + let output = input, + stripEthernet = args[0], + stripIP = args[1], + stripTCP = args[2]; + + if (stripEthernet) { + output = output.replace(/^(([0-9a-f]{4} ?){6,8}0800 ?)/igm, ""); + } + if (stripIP) { + output = output.replace(/^((45[0-9a-f]{2} ?([0-9a-f]{4} ?){9}))/igm, ""); + } + if (stripTCP) { + output = output.replace(/^([0-9a-f]{4} ?){6}((80[0-9a-f]{2} ?([0-9a-f]{4} ?){9})|(50[0-9a-f]{2} ?([0-9a-f]{4} ?){3}))/igm, ""); + } + + return output; + }, +}; + +export default Packets; diff --git a/src/css/themes/orange.css b/src/css/themes/orange.css new file mode 100644 index 00000000..88f9bfff --- /dev/null +++ b/src/css/themes/orange.css @@ -0,0 +1,278 @@ +#banner { + border-bottom: 1px solid #ddd; +} + +.title { + border-bottom: 1px solid #ddd; + font-weight: bold; + color: #424242; + background-color: #fafafa; +} + +.gutter { + background-color: #eee; + background-repeat: no-repeat; + background-position: 50%; +} + +.gutter.gutter-horizontal { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAAeCAYAAAAGos/EAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAlSURBVChTYzxz5sx/BiBgAhEgwPju3TtUEZZ79+6BGcNcDQMDACWJMFs4hNOSAAAAAElFTkSuQmCC'); + cursor: ew-resize; +} + +.gutter.gutter-vertical { + background-image: url('data:image/png;base64,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'); + cursor: ns-resize; +} + +.operation { + border: 1px solid #999; + border-top-width: 0; +} + +.op-list .operation { /*blue*/ + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +#rec-list .no-select { /*Overriding*/ + color: orange; + background-color: black; + border-color: orange; +} + +#rec-list .operation { /*green*/ + color: orange; + background-color: whitesmoke; + border-color: orange; +} + +#controls { + border-top: 1px solid #ddd; + background-color: #fafafa; +} + +.textarea-wrapper textarea, +.textarea-wrapper div { + font-family: Consolas, monospace; + font-size: inherit; +} + +.io-info { + font-family: Consolas, monospace; + font-weight: normal; + font-size: 8pt; +} + +.arg-title { + font-weight: bold; +} + +.arg-input { + height: 34px; + font-size: 15px; + line-height: 1.428571429; + color: #424242; + background-color: #fff; + border: 1px solid #ddd; + font-family: Consolas, monospace; +} + +select { + padding: 6px 8px; + height: 34px; + border: 1px solid #ddd; + background-color: #fff; + color: #424242; +} + +.arg[disabled] { + background-color: #eee; +} + +textarea.arg { + color: #424242; + font-family: Consolas, monospace; +} + +.break { + color: #b94a48 !important; + background-color: #f2dede !important; + border-color: #eed3d7 !important; +} + +.category-title { + background-color: #fafafa; + border-bottom: 1px solid #eee; + font-weight: bold; +} + +.category-title[href='#catFavourites'] { + border-bottom-color: #ddd; +} + +.category-title[aria-expanded=true] { + border-bottom-color: #ddd; +} + +.category-title.collapsed { + border-bottom-color: #eee; +} + +.category-title:hover { + color: #3a87ad; +} + +#search { + border-bottom: 1px solid #e3e3e3; +} + +.dropping-file { + border: 5px dashed #3a87ad !important; +} + +.selected-op { + color: #c09853 !important; + background-color: #fcf8e3 !important; + border-color: #fbeed5 !important; +} + +.option-item input[type=number] { + font-size: 14px; + line-height: 1.428571429; + color: #555; + background-color: #fff; + border: 1px solid #ccc; +} + +.favourites-hover { + color: #468847; + background-color: #dff0d8; + border: 2px dashed #468847 !important; + padding: 8px 8px 9px 8px; +} + +#edit-favourites-list { + border: 1px solid #bce8f1; +} + +#edit-favourites-list .operation { + border-left: none; + border-right: none; +} + +#edit-favourites-list .operation:last-child { + border-bottom: none; +} + +.subtext { + font-style: italic; + font-size: 13px; + color: #999; +} + +#save-footer { + border-bottom: 1px solid #e5e5e5; +} + +.flow-control-op { + color: #396f3a !important; + background-color: #c7e4ba !important; + border-color: #b3dba2 !important; +} + +.flow-control-op.break { + color: #94312f !important; + background-color: #eabfbf !important; + border-color: #e2aeb5 !important; +} + +#support-modal textarea { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +#save-text, +#load-text { + font-family: Consolas, monospace; +} + +button.dropdown-toggle { + background-color: #f4f4f4; +} + + + + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} +::-webkit-scrollbar-track { + background-color: #fafafa; +} +::-webkit-scrollbar-thumb { + background-color: #ccc; +} +::-webkit-scrollbar-thumb:hover { + background-color: #bbb; +} +::-webkit-scrollbar-corner { + background-color: #fafafa; +} + +.disabled { + color: #999 !important; + background-color: #dfdfdf !important; + border-color: #cdcdcd !important; +} + +.grey { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} + +.dark-blue { + color: #fff; + background-color: #428bca; + border-color: #428bca; +} + +.red { + color: #b94a48; + background-color: #f2dede; + border-color: #eed3d7; +} + +.amber { + color: #c09853; + background-color: #fcf8e3; + border-color: #fbeed5; +} + +.green { + color: #468847; + background-color: #dff0d8; + border-color: #d6e9c6; +} + +.blue { + color: #3a87ad; + background-color: #d9edf7; + border-color: #bce8f1; +} + +#input-text, +#output-text, +#output-html { + position: relative; + border-width: 0px; + margin: 0; + resize: none; + background-color: transparent; + white-space: pre-wrap; + word-wrap: break-word; + color: orange; + background-color: black; +} diff --git a/src/web/static/ga.html b/src/web/static/ga.html index e5f8db82..2edf01f5 100644 --- a/src/web/static/ga.html +++ b/src/web/static/ga.html @@ -1,11 +1,6 @@ -