├── .gitignore
├── .travis.yml
├── .vscode
└── settings.json
├── README.md
├── config-overrides.js
├── package.json
├── public
├── favicon.png
├── index.html
└── manifest.json
├── screenshot.png
├── src
├── App.js
├── App.test.js
├── Header.js
├── Rule.js
├── index.css
├── index.js
├── model
│ ├── header.js
│ ├── helper.js
│ └── rule.js
└── serviceWorker.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | /.idea/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "10"
4 | install:
5 | - yarn
6 | before_deploy:
7 | - yarn build
8 | deploy:
9 | provider: pages
10 | local_dir: build/
11 | target_branch: gh-pages
12 | skip_cleanup: true
13 | github_token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable
14 | keep_history: true
15 | on:
16 | branch: master
17 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "emmet.includeLanguages": {
3 | "javascript": "javascriptreact"
4 | },
5 | "emmet.triggerExpansionOnTab": true
6 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🧬 XRay POC Generation
2 |
3 | XRay安全评估 > POC辅助生成工具。
4 |
5 | 关于XRay:
6 |
7 | - [XRay 安全评估工具](https://github.com/chaitin/xray)
8 | - [如何编写YAML格式POC](https://chaitin.github.io/xray/#/guide/poc)
9 |
10 | 访问 即可直接使用。
11 |
12 | 
13 |
14 | ## 💻 本地运行
15 |
16 | 本地运行XRay POC Generation:
17 |
18 | ```shell script
19 | git clone https://github.com/phith0n/xray-poc-generation
20 | cd xray-poc-generation
21 | yarn
22 | yarn build
23 | ```
24 |
25 | 编译完成后,Web目录在`build/`文件夹。
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | const { override, fixBabelImports } = require('customize-cra');
2 |
3 | module.exports = override(
4 | fixBabelImports('import', {
5 | libraryName: 'antd',
6 | libraryDirectory: 'es',
7 | style: 'css',
8 | }),
9 | );
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "xray-poc-generation",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "antd": "^3.20.0",
7 | "clipboard": "^2.0.4",
8 | "clone": "^2.1.2",
9 | "immutability-helper": "^3.0.1",
10 | "js-yaml": "^3.13.1",
11 | "react": "^16.8.6",
12 | "react-dom": "^16.8.6"
13 | },
14 | "scripts": {
15 | "start": "react-app-rewired start",
16 | "build": "cross-env GENERATE_SOURCEMAP=false react-app-rewired build",
17 | "test": "react-app-rewired test",
18 | "eject": "react-app-rewired eject"
19 | },
20 | "eslintConfig": {
21 | "extends": "react-app"
22 | },
23 | "browserslist": {
24 | "production": [
25 | ">0.2%",
26 | "not dead",
27 | "not op_mini all"
28 | ],
29 | "development": [
30 | "last 1 chrome version",
31 | "last 1 firefox version",
32 | "last 1 safari version"
33 | ]
34 | },
35 | "devDependencies": {
36 | "babel-plugin-import": "^1.12.0",
37 | "cross-env": "^5.2.0",
38 | "customize-cra": "^0.4.1",
39 | "react-app-rewired": "^2.1.3",
40 | "react-scripts": "^3.0.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phith0n/xray-poc-generation/d8845430a3028138b368ab09b348b4dbe94014dc/public/favicon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | XRay POC 编写辅助工具
10 |
11 |
12 |
13 |
14 |
15 |
16 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "XRay POC Generation",
3 | "name": "XRay POC 生成器",
4 | "icons": [
5 | {
6 | "src": "favicon.png",
7 | "sizes": "32x32",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/phith0n/xray-poc-generation/d8845430a3028138b368ab09b348b4dbe94014dc/screenshot.png
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import update from 'immutability-helper';
3 | import yaml from 'js-yaml';
4 | import clone from 'clone';
5 | import ClipboardJS from 'clipboard';
6 | import { Layout, Menu, Breadcrumb, Row, Col, Form, Input, Button, Affix, notification } from 'antd';
7 | import RuleComponent from './Rule';
8 | import Rule from './model/rule'
9 | import { findObjectByIndex } from "./model/helper";
10 |
11 | const { Header, Content, Footer } = Layout;
12 |
13 | export default class App extends React.Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | name: "poc-yaml-",
18 | rules: [new Rule()],
19 | detail: {},
20 | poc: "",
21 | };
22 |
23 | this.updateRule = this.updateRule.bind(this);
24 | this.generatePOC = this.generatePOC.bind(this);
25 | this.addRule = this.addRule.bind(this);
26 | this.deleteRule = this.deleteRule.bind(this);
27 | this.notify = this.notify.bind(this);
28 | }
29 |
30 | componentDidMount() {
31 | const cjs = new ClipboardJS('#copy-btn');
32 | cjs.on('success', e => {
33 | this.notify("复制成功", "POC内容已成功复制到剪切板");
34 | })
35 | }
36 |
37 | notify(title, description) {
38 | notification.success({
39 | message: title,
40 | description: description,
41 | duration: 3,
42 | })
43 | }
44 |
45 | updateRule(index, key, value) {
46 | const i = findObjectByIndex(this.state.rules, index);
47 | if (i >= 0) {
48 | let rules = update(this.state.rules, {[i]: {[key]: {$set: value}}});
49 | this.setState({rules});
50 | }
51 | }
52 |
53 | deleteRule(index) {
54 | const i = findObjectByIndex(this.state.rules, index);
55 | if (i >= 0) {
56 | let rules = update(this.state.rules, {$splice: [[i, 1]]});
57 | this.setState({rules});
58 | }
59 | }
60 |
61 | generatePOC() {
62 | let data = {
63 | name: this.state.name,
64 | rules: clone(this.state.rules),
65 | };
66 |
67 | for (let rule of data.rules) {
68 | delete rule['index'];
69 |
70 | let headers = {};
71 | for (let header of rule.headers) {
72 | if (header['key']) {
73 | headers[header['key']] = header['value'];
74 | }
75 | }
76 |
77 | if (Object.keys(headers).length > 0) {
78 | rule.headers = headers;
79 | } else {
80 | delete rule.headers;
81 | }
82 |
83 | if (!rule.body.length) {
84 | delete rule.body;
85 | }
86 |
87 | if (!rule.path.length) {
88 | delete rule.path;
89 | }
90 |
91 | if (!rule.search.length) {
92 | delete rule.search;
93 | }
94 |
95 | rule.expression = `${rule.expression}\n`
96 | }
97 |
98 | const poc = yaml.safeDump(data);
99 | this.setState({poc});
100 | }
101 |
102 | addRule() {
103 | let rules = update(this.state.rules, {$push: [new Rule()]});
104 | this.setState({rules})
105 | }
106 |
107 | render() {
108 | const formItemLayout = {
109 | labelCol: { span: 2 },
110 | wrapperCol: { span: 22 },
111 | };
112 |
113 | return (
114 |
115 |
116 | 🧬 XRay POC Generation
117 |
124 |
125 |
126 |
127 | 首页
128 | POC生成
129 |
130 |
179 |
180 |
181 |
182 | );
183 | }
184 | }
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Icon, Input} from "antd";
3 |
4 | export default class HeaderComponent extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.changeInputValue = this.changeInputValue.bind(this);
8 | }
9 |
10 | changeInputValue(e) {
11 | this.props.updateHandler(this.props.header.index, e.target.name, e.target.value);
12 | }
13 |
14 | render() {
15 | return (
16 |
17 |
18 |
25 |
32 |
33 | {this.props.showDeleteButton ? (
34 | this.props.deleteHandler(this.props.header.index)}
39 | />
40 | ) : null}
41 | {this.props.showAddButton ? (
42 |
47 | ) : null}
48 |
49 | )
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Rule.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import update from 'immutability-helper';
3 | import {Card, Form, Input, Select, Switch, Button} from "antd";
4 | import HeaderComponent from "./Header";
5 | import Header from "./model/header";
6 | import { findObjectByIndex } from "./model/helper";
7 |
8 |
9 | export default class RuleComponent extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | pathHelp: {
14 | status: "success",
15 | help: ""
16 | },
17 | headersHelp: {
18 | status: "success",
19 | help: ""
20 | }
21 | };
22 | this.changeInputValue = this.changeInputValue.bind(this);
23 | this.changeInputValueByName = this.changeInputValueByName.bind(this);
24 | this.addHeader = this.addHeader.bind(this);
25 | this.updateHeader = this.updateHeader.bind(this);
26 | this.deleteHeader = this.deleteHeader.bind(this);
27 | }
28 |
29 | checkRule(name, value) {
30 | if (name === "path") {
31 | let pathHelp;
32 | if (!value.startsWith("/")) {
33 | pathHelp = {status: "error", help: "path需要以/开头"};
34 | } else {
35 | pathHelp = {status: "success", help: ""};
36 | }
37 |
38 | this.setState({pathHelp});
39 | } else if (name === "headers") {
40 | let headersHelp = {status: "success", help: ""};
41 | for (let header of value) {
42 | if (!header.key) {
43 | headersHelp = {status: "error", help: "HTTP头Key不能为空"};
44 | }
45 | }
46 |
47 | this.setState({headersHelp});
48 | }
49 | }
50 |
51 | changeInputValue(e) {
52 | this.checkRule(e.target.name, e.target.value);
53 | this.props.updateHandler(this.props.rule.index, e.target.name, e.target.value);
54 | }
55 |
56 | changeInputValueByName(name, value) {
57 | this.checkRule(name, value);
58 | this.props.updateHandler(this.props.rule.index, name, value);
59 | }
60 |
61 | addHeader() {
62 | let headers = update(this.props.rule.headers, {$push: [new Header()]});
63 | this.changeInputValueByName('headers', headers);
64 | }
65 |
66 | updateHeader(index, key, value) {
67 | const i = findObjectByIndex(this.props.rule.headers, index);
68 | if (i >= 0) {
69 | let headers = update(this.props.rule.headers, {[i]: {[key]: {$set: value}}});
70 | this.changeInputValueByName('headers', headers);
71 | }
72 | }
73 |
74 | deleteHeader(index) {
75 | const i = findObjectByIndex(this.props.rule.headers, index);
76 | if (i >= 0) {
77 | let headers = update(this.props.rule.headers, {$splice: [[i, 1]]});
78 | this.changeInputValueByName('headers', headers);
79 | }
80 | }
81 |
82 | render() {
83 | return (
84 |
91 |
92 |
93 |
94 | )}
95 | >
96 |
97 |
102 |
103 |
104 |
111 |
112 |
113 | {this.props.rule.headers.map((header, index) =>
114 | 1}
119 | addHandler={this.addHeader}
120 | updateHandler={this.updateHeader}
121 | deleteHandler={this.deleteHeader}
122 | />
123 | )}
124 |
125 |
126 |
127 |
128 |
129 | this.changeInputValueByName("follow_redirects", value)} />
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | )
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /* rubik-regular */
2 | @font-face {
3 | font-family: 'Rubik';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: url('//lib.baomitu.com/fonts/rubik/rubik-regular.eot'); /* IE9 Compat Modes */
7 | src: local('Rubik'), local('Rubik-Normal'),
8 | url('//lib.baomitu.com/fonts/rubik/rubik-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
9 | url('//lib.baomitu.com/fonts/rubik/rubik-regular.woff2') format('woff2'), /* Super Modern Browsers */
10 | url('//lib.baomitu.com/fonts/rubik/rubik-regular.woff') format('woff'), /* Modern Browsers */
11 | url('//lib.baomitu.com/fonts/rubik/rubik-regular.ttf') format('truetype'), /* Safari, Android, iOS */
12 | url('//lib.baomitu.com/fonts/rubik/rubik-regular.svg#Rubik') format('svg'); /* Legacy iOS */
13 | }
14 |
15 | /* work-sans-regular */
16 | @font-face {
17 | font-family: 'Work Sans';
18 | font-style: normal;
19 | font-weight: 400;
20 | src: url('//lib.baomitu.com/fonts/work-sans/work-sans-regular.eot'); /* IE9 Compat Modes */
21 | src: local('Work Sans'), local('WorkSans-Normal'),
22 | url('//lib.baomitu.com/fonts/work-sans/work-sans-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
23 | url('//lib.baomitu.com/fonts/work-sans/work-sans-regular.woff2') format('woff2'), /* Super Modern Browsers */
24 | url('//lib.baomitu.com/fonts/work-sans/work-sans-regular.woff') format('woff'), /* Modern Browsers */
25 | url('//lib.baomitu.com/fonts/work-sans/work-sans-regular.ttf') format('truetype'), /* Safari, Android, iOS */
26 | url('//lib.baomitu.com/fonts/work-sans/work-sans-regular.svg#WorkSans') format('svg'); /* Legacy iOS */
27 | }
28 |
29 |
30 | .App {
31 | text-align: center;
32 | }
33 |
34 | .logo {
35 | height: 31px;
36 | padding: 0 10px;
37 | line-height: 31px;
38 | /*font-weight: bold;*/
39 | font-size: 16px;
40 | color: #ccc;
41 | background: rgba(255, 255, 255, 0.2);
42 | margin: 16px 24px 16px 0;
43 | float: left;
44 | font-family: 'Work Sans', sans-serif;
45 | }
46 |
47 | .rule-card .rule-item {
48 | margin-bottom: 0px;
49 | }
50 |
51 | .dynamic-delete-button {
52 | cursor: pointer;
53 | position: relative;
54 | top: 4px;
55 | font-size: 24px;
56 | transition: all 0.3s;
57 | margin-left: 10px;
58 | }
59 |
60 | textarea.ant-input {
61 | resize: none;
62 | }
63 |
64 | .color-grey {
65 | color: #999;
66 | }
67 |
68 | .color-red {
69 | color: red;
70 | }
71 |
72 | textarea#poc-detail {
73 | font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
74 | }
75 |
76 | .br {
77 | margin-right: 8px;
78 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import 'antd/dist/antd.css';
4 | import './index.css';
5 | import App from './App';
6 | import * as serviceWorker from './serviceWorker';
7 |
8 | ReactDOM.render(
9 | ,
10 | document.getElementById('root')
11 | );
12 |
13 | // If you want your app to work offline and load faster, you can change
14 | // unregister() to register() below. Note this comes with some pitfalls.
15 | // Learn more about service workers: https://bit.ly/CRA-PWA
16 | serviceWorker.unregister();
17 |
--------------------------------------------------------------------------------
/src/model/header.js:
--------------------------------------------------------------------------------
1 | let nextHeaderIndex = 0;
2 |
3 | export default class HeaderObject {
4 | constructor() {
5 | this.index = ++nextHeaderIndex;
6 | this.key = "";
7 | this.value = "";
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/model/helper.js:
--------------------------------------------------------------------------------
1 | function findObjectByIndex(dataList, index) {
2 | for (let id in dataList) {
3 | if (dataList[id].index === index) {
4 | return id;
5 | }
6 | }
7 | return -1;
8 | }
9 |
10 | export { findObjectByIndex }
11 |
--------------------------------------------------------------------------------
/src/model/rule.js:
--------------------------------------------------------------------------------
1 | import Header from './header';
2 |
3 | let nextRuleIndex = 0;
4 |
5 | export default class RuleObject {
6 | constructor() {
7 | this.index = ++nextRuleIndex;
8 | this.method = "GET";
9 | this.path = "";
10 | this.headers = [new Header()];
11 | this.body = "";
12 | this.follow_redirects = true;
13 | this.expression = "";
14 | this.search = "";
15 | }
16 |
17 | addHeader() {
18 |
19 | }
20 | }
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------