4
.gitignore
vendored
@ -16,4 +16,6 @@ vendor
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*/.DS_Store
|
*/.DS_Store
|
||||||
*/.idea/%
|
*/.idea/%
|
||||||
*/vendor/%
|
*/vendor/%
|
||||||
|
db/*.db
|
||||||
|
logs/*.log
|
108
README.md
@ -1,4 +1,4 @@
|
|||||||

|

|
||||||
|
|
||||||
# 介绍
|
# 介绍
|
||||||
|
|
||||||
@ -6,48 +6,90 @@
|
|||||||
|
|
||||||
**HFish** 是一款基于 Golang 开发的跨平台多功能主动诱导型蜜罐框架系统,为了企业安全防护测试做出了精心的打造
|
**HFish** 是一款基于 Golang 开发的跨平台多功能主动诱导型蜜罐框架系统,为了企业安全防护测试做出了精心的打造
|
||||||
|
|
||||||
- 多功能 不仅仅支持 HTTP(S) 蜜罐,还支持 SSH、SFTP、Redis、Mysql 等
|
|
||||||
|
- 多功能 不仅仅支持 HTTP(S) 蜜罐,还支持 SSH、SFTP、Redis、Mysql、FTP、Telnet、暗网 等
|
||||||
- 扩展性 提供 API 接口,使用者可以随意扩展蜜罐模块 ( WEB、PC、APP )
|
- 扩展性 提供 API 接口,使用者可以随意扩展蜜罐模块 ( WEB、PC、APP )
|
||||||
- 便捷性 使用 Golang 开发,使用者可以在 Win + Mac + Linux 上快速部署一套蜜罐平台
|
- 便捷性 使用 Golang 开发,使用者可以在 Win + Mac + Linux 上快速部署一套蜜罐平台
|
||||||
|
|
||||||
# 地址
|
# 地址
|
||||||
|
|
||||||
- Github: https://github.com/hacklcx/HFish
|
## Github
|
||||||
|
|
||||||
|
- Git: https://github.com/hacklcx/HFish
|
||||||
- Download: https://github.com/hacklcx/HFish/releases
|
- Download: https://github.com/hacklcx/HFish/releases
|
||||||
|
|
||||||
|
## 码云(Gitee)
|
||||||
|
|
||||||
|
- Git: https://gitee.com/lauix/HFish
|
||||||
|
- Download: https://gitee.com/lauix/HFish/releases
|
||||||
|
|
||||||
# 快速部署
|
# 快速部署
|
||||||
|
|
||||||
### 部署说明
|
## 部署说明
|
||||||
|
|
||||||
- 下载当前系统二进制包
|
- 下载当前系统二进制包
|
||||||
- cd 到程序根目录,修改 config.ini 配置文件
|
- cd 到程序根目录,修改 config.ini 配置文件
|
||||||
- 执行 ./HFish run 启动服务
|
- 执行 ./HFish run 启动服务
|
||||||
- 浏览器输入 http://localhost:9001 打开
|
- 浏览器输入 http://localhost:9001 打开
|
||||||
|
|
||||||
### 帮助页面
|
## 集群部署
|
||||||
|
|
||||||
|
- 复制 HFish、config.ini、web(不启动WEB蜜罐可以不复制) 目录文件到服务器上
|
||||||
|
- 修改 config.ini -> rpc -> status 为 2
|
||||||
|
- 修改 config.ini -> rpc -> addr 地址为 HFish 服务端地址
|
||||||
|
|
||||||
|
## 命令行帮助
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 启动服务
|
## 启动服务
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# 部分界面展示
|
# 部分界面展示
|
||||||
|
|
||||||

|
## 登录页
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## 上钩页
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 分布式集群
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 邮件群发
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# 部分功能使用演示
|
# 部分功能使用演示
|
||||||
|
|
||||||
### Redis 蜜罐
|
## WEB 蜜罐
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## SSH 蜜罐
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Redis 蜜罐
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Mysql 蜜罐
|
## Mysql 蜜罐
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## FTP 蜜罐
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Telnet 蜜罐
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# 注意事项
|
# 注意事项
|
||||||
|
|
||||||
- 邮箱 SMTP 配置后需要开启方可使用
|
- 邮箱 SMTP 配置后需要开启方可使用
|
||||||
@ -55,9 +97,13 @@
|
|||||||
- 启动 WEB 蜜罐,请先启动 API 模块
|
- 启动 WEB 蜜罐,请先启动 API 模块
|
||||||
- WEB 插件 需在 WEB 目录下 编写
|
- WEB 插件 需在 WEB 目录下 编写
|
||||||
- WEB 插件 下面必须存在两个目录
|
- WEB 插件 下面必须存在两个目录
|
||||||
|
- 集群 心跳为60秒,断开显示会延迟60秒
|
||||||
|
- 暗网蜜罐是支持的,但是目前Tor服务网上找不到,无法提供演示
|
||||||
|
|
||||||
# API 接口
|
# API 接口
|
||||||
|
|
||||||
|
## WEB 蜜罐
|
||||||
|
|
||||||
```
|
```
|
||||||
URL: http://localhost:9001/api/v1/post/report
|
URL: http://localhost:9001/api/v1/post/report
|
||||||
|
|
||||||
@ -73,6 +119,33 @@ POST:
|
|||||||
sec_key 可在 config.ini 配置里修改,修改后 WEB 模板也需要同时修改
|
sec_key 可在 config.ini 配置里修改,修改后 WEB 模板也需要同时修改
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 暗网 蜜罐
|
||||||
|
|
||||||
|
```
|
||||||
|
URL: http://localhost:9001/api/v1/post/deep_report
|
||||||
|
|
||||||
|
POST:
|
||||||
|
|
||||||
|
name : 暗网后台蜜罐 # 项目名
|
||||||
|
info : admin&&12345 # 上报信息,&& 为换行符号
|
||||||
|
sec_key : 9cbf8a4dcb8e30682b927f352d6559a0 # API 安全密钥
|
||||||
|
|
||||||
|
特殊说明:
|
||||||
|
|
||||||
|
URL api/v1/post/deep_report 可在 config.ini 配置里修改
|
||||||
|
sec_key 可在 config.ini 配置里修改,修改后 暗网 模板也需要同时修改
|
||||||
|
```
|
||||||
|
|
||||||
|
## 黑名单IP
|
||||||
|
|
||||||
|
```
|
||||||
|
URL(Get): http://localhost:9001/api/v1/get/ip
|
||||||
|
|
||||||
|
特殊说明:
|
||||||
|
|
||||||
|
提供此接口为了配合防火墙使用,具体方案欢迎来讨论!
|
||||||
|
```
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
- [x] 登录模块
|
- [x] 登录模块
|
||||||
@ -82,21 +155,20 @@ POST:
|
|||||||
- [x] 命令行优化
|
- [x] 命令行优化
|
||||||
- [x] 支持自定义 WEB 模板
|
- [x] 支持自定义 WEB 模板
|
||||||
- [x] 支持 Mysql 服务端获取连接客户端电脑任意文件
|
- [x] 支持 Mysql 服务端获取连接客户端电脑任意文件
|
||||||
- [x] 支持 HTTP(S)、SSH、SFTP、Redis、Mysql 协议
|
- [x] 支持 HTTP(S)、SSH、SFTP、Redis、Mysql、FTP、Telnet、暗网 蜜罐
|
||||||
- [ ] 支持 FTP、Telnet、SMTP、POP3、TFTP、Oracle、VPN 等
|
- [x] 日记完善优化
|
||||||
- [ ] 暗网蜜罐支持
|
- [x] 支持分布式架构
|
||||||
|
- [x] 支持分页
|
||||||
|
- [x] 支持 ip 地理信息
|
||||||
|
- [x] 提供黑名单IP接口
|
||||||
|
- [ ] 支持 SMTP、POP3、TFTP、Oracle、VPN 等
|
||||||
- [ ] WIFI 蜜罐支持
|
- [ ] WIFI 蜜罐支持
|
||||||
- [ ] 自动化蜜罐支持
|
- [ ] 自动化蜜罐支持
|
||||||
- [ ] 蜜罐报告生成
|
- [ ] 蜜罐报告生成
|
||||||
- [ ] 支持更多的 WEB 模块
|
|
||||||
- [ ] 日记完善优化
|
|
||||||
- [ ] 邮件发送支持编辑器
|
- [ ] 邮件发送支持编辑器
|
||||||
- [ ] 支持邮件模板选择
|
- [ ] 支持邮件模板选择
|
||||||
- [ ] 蜜罐高交互完善
|
- [ ] 蜜罐高交互完善
|
||||||
- [ ] 支持 Ngrok 一键映射
|
- [ ] 支持 Ngrok 一键映射
|
||||||
- [ ] 支持分布式架构
|
|
||||||
- [ ] 支持分页
|
|
||||||
- [ ] 支持 ip 地理信息 和 地图数据展示
|
|
||||||
- [ ] 支持更多的图表统计
|
- [ ] 支持更多的图表统计
|
||||||
- [ ] Mysql 支持
|
- [ ] Mysql 支持
|
||||||
- [ ] 规划更多的功能...
|
- [ ] 规划更多的功能...
|
||||||
|
177
admin/colony.html
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
{{template "header"}}
|
||||||
|
<style>
|
||||||
|
.card-box {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tableList .td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openx {
|
||||||
|
background: #3dd209;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: -webkit-inline-box;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.closex {
|
||||||
|
background: #dc0e0e;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 100%;
|
||||||
|
display: -webkit-inline-box;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header .close {
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent_name{
|
||||||
|
font-weight: bold;
|
||||||
|
color: #167bcc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h4 class="page-title">分布式集群</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="card-box table-responsive">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="10%">集群名称</th>
|
||||||
|
<th width="10%">集群 IP</th>
|
||||||
|
<th width="10%" style="text-align: center;">WEB</th>
|
||||||
|
<th width="10%" style="text-align: center;">暗网</th>
|
||||||
|
<th width="10%" style="text-align: center;">SSH</th>
|
||||||
|
<th width="10%" style="text-align: center;">Redis</th>
|
||||||
|
<th width="10%" style="text-align: center;">Mysql</th>
|
||||||
|
<th width="10%" style="text-align: center;">Telnet</th>
|
||||||
|
<th width="10%" style="text-align: center;">FTP</th>
|
||||||
|
{{/*<th width="10%" style="text-align: center;">Http代理</th>*/}}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="tableList">
|
||||||
|
<tr style="text-align: center;">
|
||||||
|
<td style="line-height: 200px;font-size: 20px;color: #a9a9a9;" colspan="5">暂无数据</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "footer" }}
|
||||||
|
<script>
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: "/get/colony/list",
|
||||||
|
dataType: "json",
|
||||||
|
success: function (e) {
|
||||||
|
var data = e.data;
|
||||||
|
var _h = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
_h += '<tr>';
|
||||||
|
|
||||||
|
var last_update_time = data[i].last_update_time;
|
||||||
|
last_update_time = last_update_time.substring(0, 19);
|
||||||
|
last_update_time = last_update_time.replace(/-/g, '/');
|
||||||
|
var timestamp = new Date(last_update_time).getTime();
|
||||||
|
var db_time = timestamp.toString().substring(0, 10);
|
||||||
|
var curr_time = Math.round(new Date() / 1000);
|
||||||
|
|
||||||
|
if ((curr_time - parseInt(db_time)) < 60) {
|
||||||
|
_h += ' <td class="td agent_name"><span class="openx"></span>' + data[i].agent_name + '</td>';
|
||||||
|
_h += ' <td class="td"><span>' + data[i].agent_ip + '</span></td>';
|
||||||
|
|
||||||
|
if (data[i].web_status == "1") {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||||
|
} else {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[i].dart_status == "1") {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||||
|
} else {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[i].ssh_status == "1") {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||||
|
} else {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[i].redis_status == "1") {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||||
|
} else {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[i].mysql_status == "1") {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||||
|
} else {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[i].telnet_status == "1") {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||||
|
} else {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[i].ftp_status == "1") {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||||
|
} else {
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (data[i].http_status == "1") {
|
||||||
|
// _h += ' <td class="td" style="text-align: center;"><span class="openx"></span></td>';
|
||||||
|
// } else {
|
||||||
|
// _h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
_h += ' <td class="td agent_name"><span class="closex"></span>' + data[i].agent_name + '</td>';
|
||||||
|
_h += ' <td class="td"><span>' + data[i].agent_ip + '</span></td>';
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
_h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
// _h += ' <td class="td" style="text-align: center;"><span class="closex"></span></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_h += '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_h == "") {
|
||||||
|
_h = '<tr style="text-align: center;"><td style="line-height: 200px;font-size: 20px;color: #a9a9a9;" colspan="9">暂无数据</td></tr>'
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#tableList").html(_h);
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
@ -23,6 +23,35 @@
|
|||||||
display: -webkit-inline-box;
|
display: -webkit-inline-box;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lb_web {
|
||||||
|
color: #039cfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lb_ssh {
|
||||||
|
color: #953fb9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lb_redis {
|
||||||
|
color: #e44242;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lb_mysql {
|
||||||
|
color: #cabd23;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lb_ftp {
|
||||||
|
color: #32cc16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lb_telnet {
|
||||||
|
color: #6b79c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lb_deep {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
@ -36,15 +65,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||||
<div class="card-box tilebox-two">
|
<div class="card-box tilebox-two">
|
||||||
<i class="icon-rocket pull-xs-right text-muted"></i>
|
<i class="icon-ghost pull-xs-right text-muted"></i>
|
||||||
<h6 class="text-success text-uppercase m-b-15 m-t-10">WEB上钩数</h6>
|
<h6 class="lb_ssh text-uppercase m-b-15 m-t-10">SSH 蜜罐</h6>
|
||||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.webSum}}</span></h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
|
||||||
<div class="card-box tilebox-two">
|
|
||||||
<i class="icon-chart pull-xs-right text-muted"></i>
|
|
||||||
<h6 class="text-primary text-uppercase m-b-15 m-t-10">SSH上钩数</h6>
|
|
||||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.sshSum}}</span></h2>
|
<h2 class="m-b-10"><span data-plugin="counterup">{{.sshSum}}</span></h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -52,21 +74,54 @@
|
|||||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||||
<div class="card-box tilebox-two">
|
<div class="card-box tilebox-two">
|
||||||
<i class="icon-layers pull-xs-right text-muted"></i>
|
<i class="icon-layers pull-xs-right text-muted"></i>
|
||||||
<h6 class="text-pink text-uppercase m-b-15 m-t-10">Redis上钩数</h6>
|
<h6 class="lb_redis text-uppercase m-b-15 m-t-10">Redis 蜜罐</h6>
|
||||||
<h2 class="m-b-10" data-plugin="counterup">{{.redisSum}}</h2>
|
<h2 class="m-b-10" data-plugin="counterup">{{.redisSum}}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||||
<div class="card-box tilebox-two">
|
<div class="card-box tilebox-two">
|
||||||
<i class="icon-paypal pull-xs-right text-muted"></i>
|
<i class="icon-chart pull-xs-right text-muted"></i>
|
||||||
<h6 class="text-info text-uppercase m-b-15 m-t-10">Mysql上钩数</h6>
|
<h6 class="lb_mysql text-uppercase m-b-15 m-t-10">Mysql 蜜罐</h6>
|
||||||
<h2 class="m-b-10"><span data-plugin="counterup">{{.mysqlSum}}</span></h2>
|
<h2 class="m-b-10"><span data-plugin="counterup">{{.mysqlSum}}</span></h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-3">
|
||||||
|
<div class="card-box tilebox-two">
|
||||||
|
<i class="icon-layers pull-xs-right text-muted"></i>
|
||||||
|
<h6 class="lb_ftp text-uppercase m-b-15 m-t-10">FTP 蜜罐</h6>
|
||||||
|
<h2 class="m-b-10"><span data-plugin="counterup">{{.ftpSum}}</span></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-4">
|
||||||
|
<div class="card-box tilebox-two">
|
||||||
|
<i class="icon-plane pull-xs-right text-muted"></i>
|
||||||
|
<h6 class="lb_telnet text-uppercase m-b-15 m-t-10">TELNET 蜜罐</h6>
|
||||||
|
<h2 class="m-b-10"><span data-plugin="counterup">{{.telnetSum}}</span></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-4">
|
||||||
|
<div class="card-box tilebox-two">
|
||||||
|
<i class="icon-fire pull-xs-right text-muted"></i>
|
||||||
|
<h6 class="lb_web text-uppercase m-b-15 m-t-10">WEB 蜜罐</h6>
|
||||||
|
<h2 class="m-b-10"><span data-plugin="counterup">{{.webSum}}</span></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-12 col-md-6 col-lg-6 col-xl-4">
|
||||||
|
<div class="card-box tilebox-two">
|
||||||
|
<i class="icon-globe pull-xs-right text-muted"></i>
|
||||||
|
<h6 class="lb_deep text-uppercase m-b-15 m-t-10">DEEP 蜜罐</h6>
|
||||||
|
<h2 class="m-b-10"><span data-plugin="counterup">{{.deepSum}}</span></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<div class="card-box" style="height: 340px;">
|
<div class="card-box" style="height: 395px;">
|
||||||
<p class="title">服务状态</p>
|
<p class="title">服务状态</p>
|
||||||
<p><span class="openx"></span>ADMIN</p>
|
<p><span class="openx"></span>ADMIN</p>
|
||||||
|
|
||||||
@ -82,6 +137,12 @@
|
|||||||
<p><span class="closex"></span>WEB</p>
|
<p><span class="closex"></span>WEB</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if eq .deepStatus "1"}}
|
||||||
|
<p><span class="openx"></span>DEEP</p>
|
||||||
|
{{else}}
|
||||||
|
<p><span class="closex"></span>DEEP</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if eq .sshStatus "1"}}
|
{{if eq .sshStatus "1"}}
|
||||||
<p><span class="openx"></span>SSH</p>
|
<p><span class="openx"></span>SSH</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
@ -99,18 +160,46 @@
|
|||||||
{{else}}
|
{{else}}
|
||||||
<p><span class="closex"></span>MYSQL</p>
|
<p><span class="closex"></span>MYSQL</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{if eq .telnetStatus "1"}}
|
||||||
|
<p><span class="openx"></span>TELNET</p>
|
||||||
|
{{else}}
|
||||||
|
<p><span class="closex"></span>TELNET</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if eq .ftpStatus "1"}}
|
||||||
|
<p><span class="openx"></span>FTP</p>
|
||||||
|
{{else}}
|
||||||
|
<p><span class="closex"></span>FTP</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<div class="card-box">
|
<div class="card-box">
|
||||||
<div id="myChart" style="width:100%;height:300px;"></div>
|
<div id="myChart" style="width:100%;height:355px;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "footer" }}
|
{{template "footer" }}
|
||||||
<script src="/static/libs/echarts/echarts.js"></script>
|
<script src="/static/libs/echarts/echarts.js"></script>
|
||||||
|
<script src="/static/libs/moment/moment.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
var m = moment(new Date());
|
||||||
|
|
||||||
|
var arr = new Array();
|
||||||
|
|
||||||
|
for (var i = 0; i < 24; i++) {
|
||||||
|
var h = m.subtract(1, 'h').hours();
|
||||||
|
if (h >= 0 && h < 10) {
|
||||||
|
arr.push("0" + h.toString())
|
||||||
|
} else {
|
||||||
|
arr.push(h.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var xdata = arr.reverse();
|
||||||
|
|
||||||
var webData = [
|
var webData = [
|
||||||
0,
|
0,
|
||||||
@ -220,6 +309,87 @@
|
|||||||
0
|
0
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var deepData = [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
];
|
||||||
|
|
||||||
|
var ftpData = [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
];
|
||||||
|
|
||||||
|
var telnetData = [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
];
|
||||||
|
|
||||||
var myChart = echarts.init(document.getElementById('myChart'));
|
var myChart = echarts.init(document.getElementById('myChart'));
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@ -231,24 +401,43 @@
|
|||||||
var d = e.data;
|
var d = e.data;
|
||||||
|
|
||||||
for (var item in d.mysql) {
|
for (var item in d.mysql) {
|
||||||
mysqlData[parseInt(item)] = d.mysql[item];
|
var index = xdata.indexOf(item);
|
||||||
|
mysqlData[index] = d.mysql[item];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var item in d.redis) {
|
for (var item in d.redis) {
|
||||||
redisData[parseInt(item)] = d.redis[item];
|
var index = xdata.indexOf(item);
|
||||||
|
redisData[index] = d.redis[item];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var item in d.ssh) {
|
for (var item in d.ssh) {
|
||||||
sshData[parseInt(item)] = d.ssh[item];
|
var index = xdata.indexOf(item);
|
||||||
|
sshData[index] = d.ssh[item];
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var item in d.web) {
|
for (var item in d.web) {
|
||||||
webData[parseInt(item)] = d.web[item];
|
var index = xdata.indexOf(item);
|
||||||
|
webData[index] = d.web[item];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var item in d.deep) {
|
||||||
|
var index = xdata.indexOf(item);
|
||||||
|
deepData[index] = d.deep[item];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var item in d.ftp) {
|
||||||
|
var index = xdata.indexOf(item);
|
||||||
|
ftpData[index] = d.ftp[item];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var item in d.telnet) {
|
||||||
|
var index = xdata.indexOf(item);
|
||||||
|
telnetData[index] = d.telnet[item];
|
||||||
}
|
}
|
||||||
|
|
||||||
var option = {
|
var option = {
|
||||||
title: {
|
title: {
|
||||||
text: '今日数据'
|
text: '最近24小时'
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
@ -260,11 +449,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: ['WEB', 'SSH', 'REDIS', 'MYSQL']
|
data: ['WEB', 'DEEP', 'SSH', 'REDIS', 'MYSQL', 'TELNET', 'FTP']
|
||||||
},
|
},
|
||||||
grid: {
|
grid: {
|
||||||
left: '0%',
|
left: '0%',
|
||||||
right: '0%',
|
right: '2%',
|
||||||
bottom: '3%',
|
bottom: '3%',
|
||||||
containLabel: true
|
containLabel: true
|
||||||
},
|
},
|
||||||
@ -272,31 +461,7 @@
|
|||||||
{
|
{
|
||||||
type: 'category',
|
type: 'category',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
data: [
|
data: xdata
|
||||||
'00',
|
|
||||||
'01',
|
|
||||||
'02',
|
|
||||||
'03',
|
|
||||||
'04',
|
|
||||||
'05',
|
|
||||||
'06',
|
|
||||||
'07',
|
|
||||||
'08',
|
|
||||||
'09',
|
|
||||||
'10',
|
|
||||||
'12',
|
|
||||||
'13',
|
|
||||||
'14',
|
|
||||||
'15',
|
|
||||||
'16',
|
|
||||||
'17',
|
|
||||||
'18',
|
|
||||||
'19',
|
|
||||||
'20',
|
|
||||||
'21',
|
|
||||||
'22',
|
|
||||||
'23'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
yAxis: [
|
yAxis: [
|
||||||
@ -312,6 +477,13 @@
|
|||||||
areaStyle: {normal: {}},
|
areaStyle: {normal: {}},
|
||||||
data: webData
|
data: webData
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'DEEP',
|
||||||
|
type: 'line',
|
||||||
|
stack: '总量',
|
||||||
|
areaStyle: {normal: {}},
|
||||||
|
data: deepData
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'SSH',
|
name: 'SSH',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@ -332,6 +504,20 @@
|
|||||||
stack: '总量',
|
stack: '总量',
|
||||||
areaStyle: {normal: {}},
|
areaStyle: {normal: {}},
|
||||||
data: mysqlData
|
data: mysqlData
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'TELNET',
|
||||||
|
type: 'line',
|
||||||
|
stack: '总量',
|
||||||
|
areaStyle: {normal: {}},
|
||||||
|
data: telnetData
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'FTP',
|
||||||
|
type: 'line',
|
||||||
|
stack: '总量',
|
||||||
|
areaStyle: {normal: {}},
|
||||||
|
data: ftpData
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
200
admin/fish.html
@ -39,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lb_ssh {
|
.lb_ssh {
|
||||||
background-color: #434556;
|
background-color: #953fb9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lb_redis {
|
.lb_redis {
|
||||||
@ -50,6 +50,18 @@
|
|||||||
background-color: #cabd23;
|
background-color: #cabd23;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lb_ftp {
|
||||||
|
background-color: #32cc16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lb_telnet {
|
||||||
|
background-color: #6b79c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lb_deep {
|
||||||
|
background-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
.pre {
|
.pre {
|
||||||
background: #2c2c31;
|
background: #2c2c31;
|
||||||
color: #4fe21b;
|
color: #4fe21b;
|
||||||
@ -77,32 +89,79 @@
|
|||||||
.modal-header .close {
|
.modal-header .close {
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sos {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent_name {
|
||||||
|
border: 1px solid #167bcc;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #167bcc;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ipinfo{
|
||||||
|
border: 1px solid #434e56;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #434e56;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
{{/*<div class="btn-group pull-right m-t-15">*/}}
|
{{/*<div class="btn-group pull-right m-t-15">*/}}
|
||||||
{{/*<a type="button" class="btn btn-custom" href="#">导出 <span class="m-l-5"><i*/}}
|
{{/*<a type="button" class="btn btn-custom" href="#">导出 <span class="m-l-5"><i*/}}
|
||||||
{{/*class="zmdi zmdi-alarm-plus"></i></span>*/}}
|
{{/*class="zmdi zmdi-alarm-plus"></i></span>*/}}
|
||||||
{{/*</a>*/}}
|
{{/*</a>*/}}
|
||||||
{{/*</div>*/}}
|
{{/*</div>*/}}
|
||||||
<h4 class="page-title">上钩列表</h4>
|
<h4 class="page-title">上钩列表</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="sos">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<select class="form-control" id="selectType" style="height: 34px;" onchange="so()"></select>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon"><i class="fa fa-search"></i></span>
|
||||||
|
<input type="text" class="form-control" id="so_text"
|
||||||
|
placeholder="请输入搜索内容" oninput="so()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="card-box table-responsive">
|
<div class="card-box table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="10%">项目</th>
|
<th width="10%">项目</th>
|
||||||
|
<th width="10%">集群名称</th>
|
||||||
<th width="10%">来源 IP</th>
|
<th width="10%">来源 IP</th>
|
||||||
|
<th width="10%">地理信息</th>
|
||||||
<th width="10%">信息</th>
|
<th width="10%">信息</th>
|
||||||
<th width="10%">上钩时间</th>
|
<th width="8%">上钩时间</th>
|
||||||
<th width="1%">操作</th>
|
<th width="2%">操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="tableList"></tbody>
|
<tbody id="tableList"></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div style="text-align: center;" class="dataTables_paginate paging_simple_numbers">
|
||||||
|
<ul class="pagination" id="pages">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -167,41 +226,22 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init_type() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
url: "/get/fish/list",
|
url: "/get/fish/typeList",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
success: function (e) {
|
success: function (e) {
|
||||||
if (e.code == 200) {
|
if (e.code == 200) {
|
||||||
var data = e.data;
|
var data = e.data;
|
||||||
var _h = '';
|
var _h = '<option value="all">请选择类型</option>';
|
||||||
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
for (var i = 0; i < data.length; i++) {
|
||||||
_h += '<tr>';
|
_h += '<option value="' + data[i].type + '">' + data[i].type + '</option>';
|
||||||
_h += ' <td class="td">';
|
|
||||||
if (data[i].type == "WEB") {
|
|
||||||
_h += ' <span class="label label-primary">WEB</span> ';
|
|
||||||
} else if (data[i].type == "SSH") {
|
|
||||||
_h += ' <span class="label lb_ssh">SSH</span> ';
|
|
||||||
} else if (data[i].type == "REDIS") {
|
|
||||||
_h += ' <span class="label lb_redis">REDIS</span> ';
|
|
||||||
} else if (data[i].type == "MYSQL") {
|
|
||||||
_h += ' <span class="label lb_mysql">MYSQL</span> ';
|
|
||||||
}
|
|
||||||
|
|
||||||
_h += ' <span class="project">' + data[i].project_name + '</span>';
|
|
||||||
_h += ' </td>';
|
|
||||||
_h += ' <td class="td">' + data[i].ip + '</td>';
|
|
||||||
_h += ' <td><span class="info" onclick="show(' + data[i].id + ')">点击查看</span></td>';
|
|
||||||
_h += ' <td class="td">' + data[i].create_time + '</td>';
|
|
||||||
_h += ' <td class="td" style="text-align: center;">';
|
|
||||||
_h += ' <i class="fa fa-trash-o" onclick="del(' + data[i].id + ')"></i>';
|
|
||||||
_h += ' </td>';
|
|
||||||
_h += '</tr>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#tableList").html(_h);
|
$("#selectType").html(_h);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -211,6 +251,98 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function init(page, type, so_text) {
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: "/get/fish/list",
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
"page": page,
|
||||||
|
"pageSize": 10,
|
||||||
|
"type": type,
|
||||||
|
"so_text": so_text
|
||||||
|
},
|
||||||
|
success: function (e) {
|
||||||
|
var data = e.data;
|
||||||
|
var _h = '';
|
||||||
|
|
||||||
init();
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
_h += '<tr>';
|
||||||
|
_h += ' <td class="td">';
|
||||||
|
if (data[i].type == "WEB") {
|
||||||
|
_h += ' <span class="label label-primary">WEB</span> ';
|
||||||
|
} else if (data[i].type == "SSH") {
|
||||||
|
_h += ' <span class="label lb_ssh">SSH</span> ';
|
||||||
|
} else if (data[i].type == "REDIS") {
|
||||||
|
_h += ' <span class="label lb_redis">REDIS</span> ';
|
||||||
|
} else if (data[i].type == "MYSQL") {
|
||||||
|
_h += ' <span class="label lb_mysql">MYSQL</span> ';
|
||||||
|
} else if (data[i].type == "FTP") {
|
||||||
|
_h += ' <span class="label lb_ftp">FTP</span> ';
|
||||||
|
} else if (data[i].type == "TELNET") {
|
||||||
|
_h += ' <span class="label lb_telnet">TELNET</span> ';
|
||||||
|
} else if (data[i].type == "DEEP") {
|
||||||
|
_h += ' <span class="label lb_deep">DEEP</span> ';
|
||||||
|
}
|
||||||
|
|
||||||
|
_h += ' <span class="project">' + data[i].project_name + '</span>';
|
||||||
|
_h += ' </td>';
|
||||||
|
_h += ' <td class="td"><span class="agent_name">' + data[i].agent + '</span></td>';
|
||||||
|
_h += ' <td class="td" style="font-weight: bold;">' + data[i].ip + '</td>';
|
||||||
|
_h += ' <td class="td"><span class="ipinfo">' + data[i].ip_info + '</span></td>';
|
||||||
|
_h += ' <td><span class="info" onclick="show(' + data[i].id + ')">点击查看</span></td>';
|
||||||
|
_h += ' <td class="td">' + data[i].create_time + '</td>';
|
||||||
|
_h += ' <td class="td" style="text-align: center;">';
|
||||||
|
_h += ' <i class="fa fa-trash-o" onclick="del(' + data[i].id + ')"></i>';
|
||||||
|
_h += ' </td>';
|
||||||
|
_h += '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_h == "") {
|
||||||
|
_h = '<tr style="text-align: center;"><td style="line-height: 200px;font-size: 20px;color: #a9a9a9;" colspan="5">暂无数据</td></tr>'
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#tableList").html(_h);
|
||||||
|
|
||||||
|
var pageCount = e.pageCount;
|
||||||
|
var page = e.page;
|
||||||
|
var _hx = '';
|
||||||
|
|
||||||
|
for (var i = 0; i < parseInt(pageCount); i++) {
|
||||||
|
if (page == (i + 1)) {
|
||||||
|
_hx += '<li class="paginate_button page-item active">';
|
||||||
|
_hx += ' <a onclick="page(' + (i + 1) + ')" style="cursor: pointer;" aria-controls="datatable" data-dt-idx="' + (i + 1) + '" tabindex="0" class="page-link">' + (i + 1) + '</a>';
|
||||||
|
_hx += '</li>';
|
||||||
|
} else {
|
||||||
|
_hx += '<li class="paginate_button page-item">';
|
||||||
|
_hx += ' <a onclick="page(' + (i + 1) + ')" style="cursor: pointer;" aria-controls="datatable" data-dt-idx="' + (i + 1) + '" tabindex="0" class="page-link">' + (i + 1) + '</a>';
|
||||||
|
_hx += '</li>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$("#pages").html(_hx);
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init(1, "all", "");
|
||||||
|
init_type();
|
||||||
|
|
||||||
|
function so() {
|
||||||
|
var selectType = $("#selectType").val();
|
||||||
|
var so_text = $("#so_text").val();
|
||||||
|
|
||||||
|
init(1, selectType, so_text);
|
||||||
|
}
|
||||||
|
|
||||||
|
function page(p) {
|
||||||
|
var selectType = $("#selectType").val();
|
||||||
|
var so_text = $("#so_text").val();
|
||||||
|
|
||||||
|
init(p, selectType, so_text);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
@ -25,10 +25,16 @@
|
|||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane fade in active" id="home-2">
|
<div class="tab-pane fade in active" id="home-2">
|
||||||
<div class="timeline-2">
|
<div class="timeline-2">
|
||||||
|
<div class="time-item">
|
||||||
|
<div class="item-info">
|
||||||
|
<small class="text-muted">2019-08-12</small>
|
||||||
|
<p>发布 v0.2 版本</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="time-item">
|
<div class="time-item">
|
||||||
<div class="item-info">
|
<div class="item-info">
|
||||||
<small class="text-muted">2019-08-05</small>
|
<small class="text-muted">2019-08-05</small>
|
||||||
<p>发布第一版</p>
|
<p>发布 v0.1 版本</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="time-item">
|
<div class="time-item">
|
||||||
@ -49,6 +55,8 @@
|
|||||||
<script src="/static/js/bootstrap.min.js"></script>
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
<script src="/static/libs/bootstrap-sweetalert/sweet-alert.min.js"></script>
|
<script src="/static/libs/bootstrap-sweetalert/sweet-alert.min.js"></script>
|
||||||
<script src="/static/libs/switchery/switchery.min.js"></script>
|
<script src="/static/libs/switchery/switchery.min.js"></script>
|
||||||
|
<script src="/static/libs/waypoints/lib/jquery.waypoints.js"></script>
|
||||||
|
<script src="/static/libs/counterup/jquery.counterup.min.js"></script>
|
||||||
|
|
||||||
<!-- App js -->
|
<!-- App js -->
|
||||||
<script src="/static/js/jquery.core.js"></script>
|
<script src="/static/js/jquery.core.js"></script>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||||
<title>HFish - 一款企业安全主动攻击型蜜罐钓鱼框架系统</title>
|
<title>HFish - 扩展企业安全测试主动诱导型蜜罐框架系统</title>
|
||||||
<link href="/static/css/style.css" rel="stylesheet" type="text/css"/>
|
<link href="/static/css/style.css" rel="stylesheet" type="text/css"/>
|
||||||
<link href="/static/libs/bootstrap-sweetalert/sweet-alert.css" rel="stylesheet" type="text/css"/>
|
<link href="/static/libs/bootstrap-sweetalert/sweet-alert.css" rel="stylesheet" type="text/css"/>
|
||||||
<link href="/static/libs/switchery/switchery.min.css" rel="stylesheet"/>
|
<link href="/static/libs/switchery/switchery.min.css" rel="stylesheet"/>
|
||||||
@ -35,7 +35,10 @@
|
|||||||
<a href="/fish"><i class="fa fa-user-secret "></i> <span> 上钩列表 </span> </a>
|
<a href="/fish"><i class="fa fa-user-secret "></i> <span> 上钩列表 </span> </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/mail"><i class="zmdi zmdi-email icotop3"></i> <span> 邮箱群发 </span> </a>
|
<a href="/colony"><i class="fa fa-cloud"></i> <span> 分布式集群 </span> </a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/mail"><i class="zmdi zmdi-email icotop3"></i> <span> 邮件群发 </span> </a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/setting"><i class="fa fa-cog icotop3"></i> <span> 系统设置 </span> </a>
|
<a href="/setting"><i class="fa fa-cog icotop3"></i> <span> 系统设置 </span> </a>
|
||||||
@ -53,7 +56,7 @@
|
|||||||
<a class="nav-link dropdown-toggle arrow-none waves-effect waves-light nav-user"
|
<a class="nav-link dropdown-toggle arrow-none waves-effect waves-light nav-user"
|
||||||
data-toggle="dropdown" href="#" role="button"
|
data-toggle="dropdown" href="#" role="button"
|
||||||
aria-haspopup="false" aria-expanded="false">
|
aria-haspopup="false" aria-expanded="false">
|
||||||
<img src="/static/images/avatar.png" alt="user" class="img-circle">
|
<img src="/static/images/avatar.png" alt="user" class="img-circle avatarx">
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-menu dropdown-menu-right dropdown-arrow profile-dropdown "
|
<div class="dropdown-menu dropdown-menu-right dropdown-arrow profile-dropdown "
|
||||||
aria-labelledby="Preview">
|
aria-labelledby="Preview">
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="shortcut icon" href="/static/favicon.ico">
|
<link rel="shortcut icon" href="/static/favicon.ico">
|
||||||
<title>HFish - 一款企业安全主动攻击型蜜罐钓鱼框架系统</title>
|
<title>HFish - 扩展企业安全测试主动诱导型蜜罐框架系统</title>
|
||||||
<link href="/static/css/style.css" rel="stylesheet" type="text/css"/>
|
<link href="/static/css/style.css" rel="stylesheet" type="text/css"/>
|
||||||
<link href="/static/libs/bootstrap-sweetalert/sweet-alert.css" rel="stylesheet" type="text/css"/>
|
<link href="/static/libs/bootstrap-sweetalert/sweet-alert.css" rel="stylesheet" type="text/css"/>
|
||||||
<style>
|
<style>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{{template "header"}}
|
{{template "header"}}
|
||||||
|
<script type="text/javascript" src="/static/libs/wangeditor3/wangEditor.css"></script>
|
||||||
<style>
|
<style>
|
||||||
.card-box {
|
.card-box {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
@ -39,6 +40,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Page-Title -->
|
<!-- Page-Title -->
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
@ -71,14 +73,20 @@
|
|||||||
<label for="taskExplain" class="col-sm-3 form-control-label"><span class="text-danger">*</span>
|
<label for="taskExplain" class="col-sm-3 form-control-label"><span class="text-danger">*</span>
|
||||||
收件人:</label>
|
收件人:</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="text" class="form-control" id="emails" placeholder="请输入收件人 例:xxx@gmail.com,xxx@outlook.com">
|
<input type="text" class="form-control" id="emails"
|
||||||
|
placeholder="请输入收件人 例:xxx@gmail.com,xxx@outlook.com">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<h4 class="header-title m-t-0 m-b-30 title">邮件模板</h4>
|
<h4 class="header-title m-t-0 m-b-30 title">邮件模板</h4>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<textarea style="width: 100%;margin-top: 20px;height: 200px;" id="content"></textarea>
|
<div class="clear"></div>
|
||||||
|
|
||||||
|
<div id="editorx">
|
||||||
|
<p>请在此处输入发送的邮件内容</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" class="btn btn-primary waves-effect waves-light"
|
<button type="button" class="btn btn-primary waves-effect waves-light"
|
||||||
style="width: 100%;margin-top: 20px;" onclick="sendMailToUsers()">发送邮件
|
style="width: 100%;margin-top: 20px;" onclick="sendMailToUsers()">发送邮件
|
||||||
</button>
|
</button>
|
||||||
@ -87,21 +95,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{template "footer" }}
|
{{template "footer" }}
|
||||||
|
<script type="text/javascript" src="/static/libs/wangeditor3/wangEditor.js"></script>
|
||||||
<script>
|
<script>
|
||||||
var sendMailToUsers=function () {
|
|
||||||
|
var E = window.wangEditor;
|
||||||
|
var editorx = new E('#editorx');
|
||||||
|
editorx.create();
|
||||||
|
|
||||||
|
var sendMailToUsers = function () {
|
||||||
var title = $("#title").val();
|
var title = $("#title").val();
|
||||||
var from = $("#from").val();
|
var from = $("#from").val();
|
||||||
var emails= $("#emails").val();
|
var emails = $("#emails").val();
|
||||||
var content=$("#content").val();
|
|
||||||
$.ajax({
|
var content = editorx.txt.html();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
url: "/post/mail/sendEmail",
|
url: "/post/mail/sendEmail",
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
data: {
|
data: {
|
||||||
title,
|
"title": title,
|
||||||
from,
|
"from": from,
|
||||||
emails,
|
"emails": emails,
|
||||||
content
|
"content": content
|
||||||
},
|
},
|
||||||
success: function (e) {
|
success: function (e) {
|
||||||
if (e.code == 200) {
|
if (e.code == 200) {
|
||||||
@ -112,7 +128,7 @@
|
|||||||
},
|
},
|
||||||
error: function (e) {
|
error: function (e) {
|
||||||
swal("发送失败", "请 Github 提交 Issues", 'error');
|
swal("发送失败", "请 Github 提交 Issues", 'error');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -17,6 +17,26 @@
|
|||||||
color: #ff5d48 !important;
|
color: #ff5d48 !important;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
line-height: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yes_config {
|
||||||
|
border: 1px solid #0f8ce6;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #0f8ce6;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no_config {
|
||||||
|
border: 1px solid #434e56;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #434e56;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Page-Title -->
|
<!-- Page-Title -->
|
||||||
@ -40,14 +60,14 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="tableList">
|
<tbody id="tableList">
|
||||||
{{range $i, $e := .dataList}}
|
{{range $i, $e := .dataList}}
|
||||||
<tr>
|
<tr id="tr{{$e.type}}" data-id="{{$e.id}}">
|
||||||
<td>{{$e.setting_name}} </td>
|
<td style="font-weight: bold;">{{$e.setting_name}} </td>
|
||||||
<td>{{$e.setting_dis}}</td>
|
<td>{{$e.setting_dis}}</td>
|
||||||
<td>{{$e.update_time}}</td>
|
<td>{{$e.update_time}}</td>
|
||||||
<td>{{if ne $e.info ""}}
|
<td>{{if ne $e.info ""}}
|
||||||
已配置
|
<span class="yes_config">已配置</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
未配置
|
<span class="no_config">未配置</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</td>
|
</td>
|
||||||
<td><input type="checkbox" id="checkbox-{{$e.id}}" data-plugin="switchery"
|
<td><input type="checkbox" id="checkbox-{{$e.id}}" data-plugin="switchery"
|
||||||
@ -89,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for=""><span class="text-danger">*</span>密码</label>
|
<label for=""><span class="text-danger">*</span>密码</label>
|
||||||
<input type="text" class="form-control" id="pass" name="pass" placeholder="请填写发送人密码">
|
<input type="password" class="form-control" id="pass" name="pass" placeholder="请填写发送人密码">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for=""><span class="text-danger">*</span>SMTP</label>
|
<label for=""><span class="text-danger">*</span>SMTP</label>
|
||||||
@ -106,12 +126,53 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Modal -->
|
||||||
|
<div class="modal fade" id="receiveEmailModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<h4 class="modal-title" id="myModalLabel">E-mail 通知设置</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for=""><span class="text-danger">*</span>发送人(账号)</label>
|
||||||
|
<input type="email" class="form-control" id="alertEmail" name="alertEmail" placeholder="请填写发送人账号">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for=""><span class="text-danger">*</span>密码</label>
|
||||||
|
<input type="password" class="form-control" id="alertPass" name="alertPass" placeholder="请填写发送人密码">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for=""><span class="text-danger">*</span>SMTP</label>
|
||||||
|
<input type="text" class="form-control" id="alertHost" name="alertHost"
|
||||||
|
placeholder="例:smtp.126.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for=""><span class="text-danger">*</span>端口</label>
|
||||||
|
<input type="number" class="form-control" id="alertPort" name="alertPort" placeholder="例:465">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for=""><span class="text-danger">*</span>接收人</label>
|
||||||
|
<textarea class="form-control" id="receive" name="receive" placeholder="接收人(群发请换行)" cols="30"
|
||||||
|
rows="10"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-primary" onclick="syncStmpConfig()">同步STMP配置</button>
|
||||||
|
<button type="submit" class="btn btn-primary" onclick="updateReceiveEmailPost()">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<input type="hidden" value="" id="settingId">
|
<input type="hidden" value="" id="settingId">
|
||||||
{{template "footer" }}
|
{{template "footer" }}
|
||||||
<script>
|
<script>
|
||||||
function settingSubFunc(id, type) {
|
function settingSubFunc(id, type) {
|
||||||
$("#settingId").val(id)
|
$("#settingId").val(id)
|
||||||
getSettingInfo(id, type)
|
getSettingInfo(id)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "mail":
|
case "mail":
|
||||||
$('#myModal').modal('show')
|
$('#myModal').modal('show')
|
||||||
@ -119,12 +180,24 @@
|
|||||||
case "login":
|
case "login":
|
||||||
$('#settingLoginModal').modal('show')
|
$('#settingLoginModal').modal('show')
|
||||||
break
|
break
|
||||||
|
case "alertMail":
|
||||||
|
$('#receiveEmailModal').modal('show')
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var getSettingInfo = function (id, type) {
|
var syncStmpConfig = function () {
|
||||||
|
var id = $("#trmail ").data("id")
|
||||||
|
getSettingInfo(id,function () {
|
||||||
|
$("#alertEmail").val($("#email").val())
|
||||||
|
$("#alertPass").val($("#pass").val())
|
||||||
|
$("#alertHost").val($("#host").val())
|
||||||
|
$("#alertPort").val($("#port").val())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var getSettingInfo = function (id,cb) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "get",
|
type: "get",
|
||||||
url: "/get/setting/info?id=" + id
|
url: "/get/setting/info?id=" + id
|
||||||
@ -139,7 +212,18 @@
|
|||||||
} else if (arr.length == 2 && result.data[0].type == "login") {
|
} else if (arr.length == 2 && result.data[0].type == "login") {
|
||||||
$("#loginName").val(arr[0])
|
$("#loginName").val(arr[0])
|
||||||
$("#loginPwd").val(arr[1])
|
$("#loginPwd").val(arr[1])
|
||||||
|
} else if (arr.length >= 4 && result.data[0].type == "alertMail") {
|
||||||
|
$("#alertEmail").val(arr[2])
|
||||||
|
$("#alertPass").val(arr[3])
|
||||||
|
$("#alertHost").val(arr[0])
|
||||||
|
$("#alertPort").val(arr[1])
|
||||||
|
var emailArr = arr.splice(4)
|
||||||
|
var content = emailArr.join("\n")
|
||||||
|
$("#receive").text(content)
|
||||||
}
|
}
|
||||||
|
if(typeof cb=="function"){
|
||||||
|
cb()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}).fail(function (err) {
|
}).fail(function (err) {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
@ -172,6 +256,34 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateReceiveEmailPost() {
|
||||||
|
|
||||||
|
var params = {
|
||||||
|
email: $("#alertEmail").val(),
|
||||||
|
pass: $("#alertPass").val(),
|
||||||
|
host: $("#alertHost").val(),
|
||||||
|
port: $("#alertPort").val(),
|
||||||
|
receive: $("#receive").val().split(/[\s\n]/).join(","),
|
||||||
|
id: $("#settingId").val()
|
||||||
|
};
|
||||||
|
console.log(params)
|
||||||
|
$.ajax({
|
||||||
|
type: "post",
|
||||||
|
url: "/post/setting/updateAlertMail",
|
||||||
|
data: params
|
||||||
|
}).success(function (result) {
|
||||||
|
if (result.code == 200) {
|
||||||
|
if (!$("#checkbox-" + params.id).prop("checked")) {
|
||||||
|
$("#checkbox-" + params.id).click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('#receiveEmailModal').modal('hide')
|
||||||
|
|
||||||
|
}).fail(function (err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function updateStatusFunc(id, ele) {
|
function updateStatusFunc(id, ele) {
|
||||||
var params = {
|
var params = {
|
||||||
"id": id,
|
"id": id,
|
||||||
|
33
config.ini
@ -1,20 +1,35 @@
|
|||||||
[admin]
|
[rpc]
|
||||||
|
status = 0 # 模式 0关闭 1服务端 2客户端
|
||||||
|
addr = 127.0.0.1:7879 # RPC 服务端地址 or 客户端地址
|
||||||
|
name = Server # 状态1 服务端 名称 状态2 客户端 名称
|
||||||
|
|
||||||
|
[admin] # RPC 状态为2 集群客户端的时候 admin 可以删掉
|
||||||
addr = 127.0.0.1:9001 # 管理后台启动地址
|
addr = 127.0.0.1:9001 # 管理后台启动地址
|
||||||
account = admin # 登录账号
|
account = admin # 登录账号
|
||||||
password = admin # 登录密码
|
password = admin # 登录密码
|
||||||
|
|
||||||
[api]
|
[api]
|
||||||
status = 1 # 是否启动 API 1 启动 0 关闭
|
status = 1 # 是否启动 API 1 启动 0 关闭
|
||||||
url = /api/v1/post/report # 管理后台启动地址
|
web_url = /api/v1/post/report # 管理后台启动地址
|
||||||
|
deep_url = /api/v1/post/deep_report # 管理后台启动地址
|
||||||
sec_key = 9cbf8a4dcb8e30682b927f352d6559a0 # API 认证秘钥
|
sec_key = 9cbf8a4dcb8e30682b927f352d6559a0 # API 认证秘钥
|
||||||
|
|
||||||
[web]
|
[web]
|
||||||
status = 0 # 是否启动 WEB 1 启动 0 关闭, 启动 API 后 WEB 方可上报结果
|
status = 0 # 是否启动 WEB 1 启动 0 关闭, 启动 API 后 WEB 方可上报结果
|
||||||
addr = 0.0.0.0:9000 # WEB 启动地址,0.0.0.0 对外开放,127.0.0.1 对内开放 可走 Nginx 反向代理
|
addr = 0.0.0.0:9000 # WEB 启动地址,0.0.0.0 对外开放,127.0.0.1 对内开放 可走 Nginx 反向代理
|
||||||
template = github/html # WEB 模板路径
|
template = wordPress/html # WEB 模板路径
|
||||||
static = github/static # WEB 静态文件路径 注意:必须存在两个目录,html 文件 和静态文件 不能平级
|
index = index.html # WEB 首页文件
|
||||||
|
static = wordPress/static # WEB 静态文件路径 注意:必须存在两个目录,html 文件 和静态文件 不能平级
|
||||||
url = / # WEB 访问目录,默认 / 可更改成 index.html index.asp index.php
|
url = / # WEB 访问目录,默认 / 可更改成 index.html index.asp index.php
|
||||||
|
|
||||||
|
[deep]
|
||||||
|
status = 0 # 是否启动 暗网 1 启动 0 关闭, 启动 API 后 方可上报结果
|
||||||
|
addr = 0.0.0.0:9002 # 暗网 WEB 启动地址
|
||||||
|
template = deep/html # 暗网 WEB 模板路径
|
||||||
|
index = index.html # 暗网 WEB 首页文件
|
||||||
|
static = deep/static # 暗网 WEB 静态文件路径 注意:必须存在两个目录,html 文件 和静态文件 不能平级
|
||||||
|
url = / # 暗网 WEB 访问目录,默认 / 可更改成 index.html index.asp index.php
|
||||||
|
|
||||||
[ssh]
|
[ssh]
|
||||||
status = 0 # 是否启动 SSH 1 启动 0 关闭
|
status = 0 # 是否启动 SSH 1 启动 0 关闭
|
||||||
addr = 0.0.0.0:22 # SSH 服务端地址 注意端口冲突,请先关闭服务器 openssh 服务 或 修改端口
|
addr = 0.0.0.0:22 # SSH 服务端地址 注意端口冲突,请先关闭服务器 openssh 服务 或 修改端口
|
||||||
@ -26,4 +41,12 @@ addr = 0.0.0.0:6379 # Redis 服务端地址 注意端
|
|||||||
[mysql]
|
[mysql]
|
||||||
status = 0 # 是否启动 Mysql 1 启动 0 关闭
|
status = 0 # 是否启动 Mysql 1 启动 0 关闭
|
||||||
addr = 0.0.0.0:3306 # Mysql 服务端地址 注意端口冲突
|
addr = 0.0.0.0:3306 # Mysql 服务端地址 注意端口冲突
|
||||||
files = /etc/passwd,/etc/group # Mysql 服务端读取客户端任意文件; 多写逗号分隔,会随机取
|
files = /etc/passwd,/etc/group # Mysql 服务端读取客户端任意文件; 多写逗号分隔,会随机取
|
||||||
|
|
||||||
|
[telnet]
|
||||||
|
status = 0 # 是否启动 Telnet 1 启动 0 关闭
|
||||||
|
addr = 0.0.0.0:23 # Telnet 服务端地址 注意端口冲突
|
||||||
|
|
||||||
|
[ftp]
|
||||||
|
status = 0 # 是否启动 Ftp 1 启动 0 关闭
|
||||||
|
addr = 0.0.0.0:21 # Ftp 服务端地址 注意端口冲突
|
@ -4,6 +4,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
"HFish/error"
|
"HFish/error"
|
||||||
|
"HFish/utils/try"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 连接数据库
|
// 连接数据库
|
||||||
@ -45,17 +46,20 @@ func Insert(sql string, args ...interface{}) int64 {
|
|||||||
db := conn()
|
db := conn()
|
||||||
stmt, _ := db.Prepare(sql)
|
stmt, _ := db.Prepare(sql)
|
||||||
|
|
||||||
res, err := stmt.Exec(args...)
|
var id int64
|
||||||
error.Check(err, "插入数据失败")
|
id = 0
|
||||||
|
|
||||||
defer stmt.Close()
|
try.Try(func() {
|
||||||
|
res, _ := stmt.Exec(args...)
|
||||||
|
//error.Check(err, "插入数据失败")
|
||||||
|
|
||||||
id, err := res.LastInsertId()
|
defer stmt.Close()
|
||||||
error.Check(err, "获取插入ID失败")
|
|
||||||
|
|
||||||
defer db.Close()
|
id, _ = res.LastInsertId()
|
||||||
|
|
||||||
|
defer db.Close()
|
||||||
|
}).Catch(func() {})
|
||||||
|
|
||||||
// 返回 自增长 ID
|
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
106
core/protocol/ftp/ftp.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package ftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"strconv"
|
||||||
|
"HFish/core/protocol/ftp/graval"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fileOne = "This is the first file available for download.\n\nBy Jàmes"
|
||||||
|
fileTwo = "This is file number two.\n\n2012-12-04"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemDriver struct{}
|
||||||
|
|
||||||
|
func (driver *MemDriver) Authenticate(user string, pass string) bool {
|
||||||
|
//return user == "test" && pass == "1234"
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) Bytes(path string) (bytes int) {
|
||||||
|
switch path {
|
||||||
|
case "/one.txt":
|
||||||
|
bytes = len(fileOne)
|
||||||
|
break
|
||||||
|
case "/files/two.txt":
|
||||||
|
bytes = len(fileTwo)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
bytes = -1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) ModifiedTime(path string) (time.Time, error) {
|
||||||
|
return time.Now(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) ChangeDir(path string) bool {
|
||||||
|
return path == "/" || path == "/files"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) DirContents(path string) (files []os.FileInfo) {
|
||||||
|
files = []os.FileInfo{}
|
||||||
|
switch path {
|
||||||
|
case "/":
|
||||||
|
files = append(files, graval.NewDirItem("files"))
|
||||||
|
files = append(files, graval.NewFileItem("one.txt", len(fileOne)))
|
||||||
|
case "/files":
|
||||||
|
files = append(files, graval.NewFileItem("two.txt", len(fileOne)))
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) DeleteDir(path string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) DeleteFile(path string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) Rename(fromPath string, toPath string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) MakeDir(path string) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) GetFile(path string) (data string, err error) {
|
||||||
|
switch path {
|
||||||
|
case "/one.txt":
|
||||||
|
data = fileOne
|
||||||
|
case "/files/two.txt":
|
||||||
|
data = fileTwo
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *MemDriver) PutFile(destPath string, data io.Reader) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type MemDriverFactory struct{}
|
||||||
|
|
||||||
|
func (factory *MemDriverFactory) NewDriver() (graval.FTPDriver, error) {
|
||||||
|
return &MemDriver{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(addr string) {
|
||||||
|
arr := strings.Split(addr, ":")
|
||||||
|
port, _ := strconv.Atoi(arr[1])
|
||||||
|
|
||||||
|
factory := &MemDriverFactory{}
|
||||||
|
ftpServer := graval.NewFTPServer(&graval.FTPServerOpts{Factory: factory, Hostname: arr[0], Port: port})
|
||||||
|
|
||||||
|
err := ftpServer.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print(err)
|
||||||
|
}
|
||||||
|
}
|
20
core/protocol/ftp/graval/MIT-LICENSE
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2008 James Healy
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
93
core/protocol/ftp/graval/README.markdown
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# graval
|
||||||
|
|
||||||
|
An experimental FTP server framework. By providing a simple driver class that
|
||||||
|
responds to a handful of methods you can have a complete FTP server.
|
||||||
|
|
||||||
|
Some sample use cases include persisting data to:
|
||||||
|
|
||||||
|
* an Amazon S3 bucket
|
||||||
|
* a relational database
|
||||||
|
* redis
|
||||||
|
* memory
|
||||||
|
|
||||||
|
There is a sample in-memory driver available - see the usage instructions below
|
||||||
|
for the steps to use it.
|
||||||
|
|
||||||
|
Full documentation for the package is available on [godoc](http://godoc.org/github.com/yob/graval)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
go get github.com/yob/graval
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To boot an FTP server you will need to provide a driver that speaks to your
|
||||||
|
persistence layer - the required driver contract is listed below.
|
||||||
|
|
||||||
|
There is a sample in-memory driver available as a demo. You can build it with
|
||||||
|
this command:
|
||||||
|
|
||||||
|
go install github.com/yob/graval/graval-mem
|
||||||
|
|
||||||
|
Then run it:
|
||||||
|
|
||||||
|
./bin/graval-mem
|
||||||
|
|
||||||
|
And finally, connect to the server with any FTP client and the following
|
||||||
|
details:
|
||||||
|
|
||||||
|
host: 127.0.0.1
|
||||||
|
username: test
|
||||||
|
password: 1234
|
||||||
|
|
||||||
|
### The Driver Contract
|
||||||
|
|
||||||
|
Your driver MUST implement a number of simple methods. You can view the required
|
||||||
|
contract in the package docs on [godoc](http://godoc.org/github.com/yob/graval)
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
* James Healy <james@yob.id.au> [http://www.yob.id.au](http://www.yob.id.au)
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
|
||||||
|
FTP is an incredibly insecure protocol. Be careful about forcing users to authenticate
|
||||||
|
with a username or password that are important.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This library is distributed under the terms of the MIT License. See the included file for
|
||||||
|
more detail.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
All suggestions and patches welcome, preferably via a git repository I can pull from.
|
||||||
|
If this library proves useful to you, please let me know.
|
||||||
|
|
||||||
|
## Further Reading
|
||||||
|
|
||||||
|
There are a range of RFCs that together specify the FTP protocol. In chronological
|
||||||
|
order, the more useful ones are:
|
||||||
|
|
||||||
|
* [http://tools.ietf.org/rfc/rfc959.txt](http://tools.ietf.org/rfc/rfc959.txt)
|
||||||
|
* [http://tools.ietf.org/rfc/rfc1123.txt](http://tools.ietf.org/rfc/rfc1123.txt)
|
||||||
|
* [http://tools.ietf.org/rfc/rfc2228.txt](http://tools.ietf.org/rfc/rfc2228.txt)
|
||||||
|
* [http://tools.ietf.org/rfc/rfc2389.txt](http://tools.ietf.org/rfc/rfc2389.txt)
|
||||||
|
* [http://tools.ietf.org/rfc/rfc2428.txt](http://tools.ietf.org/rfc/rfc2428.txt)
|
||||||
|
* [http://tools.ietf.org/rfc/rfc3659.txt](http://tools.ietf.org/rfc/rfc3659.txt)
|
||||||
|
* [http://tools.ietf.org/rfc/rfc4217.txt](http://tools.ietf.org/rfc/rfc4217.txt)
|
||||||
|
|
||||||
|
For an english summary that's somewhat more legible than the RFCs, and provides
|
||||||
|
some commentary on what features are actually useful or relevant 24 years after
|
||||||
|
RFC959 was published:
|
||||||
|
|
||||||
|
* [http://cr.yp.to/ftp.html](http://cr.yp.to/ftp.html)
|
||||||
|
|
||||||
|
For a history lesson, check out Appendix III of RCF959. It lists the preceding
|
||||||
|
(obsolete) RFC documents that relate to file transfers, including the ye old
|
||||||
|
RFC114 from 1971, "A File Transfer Protocol"
|
||||||
|
|
||||||
|
This library is heavily based on [em-ftpd](https://github.com/yob/em-ftpd), an FTPd
|
||||||
|
framework with similar design goals within the ruby and EventMachine ecosystems. It
|
||||||
|
worked well enough, but you know, callbacks and event loops make me something
|
||||||
|
something.
|
672
core/protocol/ftp/graval/commands.go
Normal file
@ -0,0 +1,672 @@
|
|||||||
|
package graval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/jehiah/go-strftime"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"HFish/core/report"
|
||||||
|
"HFish/utils/is"
|
||||||
|
"HFish/core/rpc/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ftpCommand interface {
|
||||||
|
RequireParam() bool
|
||||||
|
RequireAuth() bool
|
||||||
|
Execute(*ftpConn, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type commandMap map[string]ftpCommand
|
||||||
|
|
||||||
|
var (
|
||||||
|
commands = commandMap{
|
||||||
|
"ALLO": commandAllo{},
|
||||||
|
"CDUP": commandCdup{},
|
||||||
|
"CWD": commandCwd{},
|
||||||
|
"DELE": commandDele{},
|
||||||
|
"EPRT": commandEprt{},
|
||||||
|
"EPSV": commandEpsv{},
|
||||||
|
"LIST": commandList{},
|
||||||
|
"NLST": commandNlst{},
|
||||||
|
"MDTM": commandMdtm{},
|
||||||
|
"MKD": commandMkd{},
|
||||||
|
"MODE": commandMode{},
|
||||||
|
"NOOP": commandNoop{},
|
||||||
|
"PASS": commandPass{},
|
||||||
|
"PASV": commandPasv{},
|
||||||
|
"PORT": commandPort{},
|
||||||
|
"PWD": commandPwd{},
|
||||||
|
"QUIT": commandQuit{},
|
||||||
|
"RETR": commandRetr{},
|
||||||
|
"RNFR": commandRnfr{},
|
||||||
|
"RNTO": commandRnto{},
|
||||||
|
"RMD": commandRmd{},
|
||||||
|
"SIZE": commandSize{},
|
||||||
|
"STOR": commandStor{},
|
||||||
|
"STRU": commandStru{},
|
||||||
|
"SYST": commandSyst{},
|
||||||
|
"TYPE": commandType{},
|
||||||
|
"USER": commandUser{},
|
||||||
|
"XCUP": commandCdup{},
|
||||||
|
"XCWD": commandCwd{},
|
||||||
|
"XPWD": commandPwd{},
|
||||||
|
"XRMD": commandRmd{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// commandAllo responds to the ALLO FTP command.
|
||||||
|
//
|
||||||
|
// This is essentially a ping from the client so we just respond with an
|
||||||
|
// basic OK message.
|
||||||
|
type commandAllo struct{}
|
||||||
|
|
||||||
|
func (cmd commandAllo) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandAllo) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandAllo) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.writeMessage(202, "Obsolete")
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmdCdup responds to the CDUP FTP command.
|
||||||
|
//
|
||||||
|
// Allows the client change their current directory to the parent.
|
||||||
|
type commandCdup struct{}
|
||||||
|
|
||||||
|
func (cmd commandCdup) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandCdup) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandCdup) Execute(conn *ftpConn, param string) {
|
||||||
|
otherCmd := &commandCwd{}
|
||||||
|
otherCmd.Execute(conn, "..")
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandCwd responds to the CWD FTP command. It allows the client to change the
|
||||||
|
// current working directory.
|
||||||
|
type commandCwd struct{}
|
||||||
|
|
||||||
|
func (cmd commandCwd) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandCwd) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandCwd) Execute(conn *ftpConn, param string) {
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
if conn.driver.ChangeDir(path) {
|
||||||
|
conn.namePrefix = path
|
||||||
|
conn.writeMessage(250, "Directory changed to "+path)
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(550, "Action not taken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandDele responds to the DELE FTP command. It allows the client to delete
|
||||||
|
// a file
|
||||||
|
type commandDele struct{}
|
||||||
|
|
||||||
|
func (cmd commandDele) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandDele) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandDele) Execute(conn *ftpConn, param string) {
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
if conn.driver.DeleteFile(path) {
|
||||||
|
conn.writeMessage(250, "File deleted")
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(550, "Action not taken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandEprt responds to the EPRT FTP command. It allows the client to
|
||||||
|
// request an active data socket with more options than the original PORT
|
||||||
|
// command. It mainly adds ipv6 support.
|
||||||
|
type commandEprt struct{}
|
||||||
|
|
||||||
|
func (cmd commandEprt) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandEprt) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandEprt) Execute(conn *ftpConn, param string) {
|
||||||
|
delim := string(param[0:1])
|
||||||
|
parts := strings.Split(param, delim)
|
||||||
|
addressFamily, err := strconv.Atoi(parts[1])
|
||||||
|
host := parts[2]
|
||||||
|
port, err := strconv.Atoi(parts[3])
|
||||||
|
if addressFamily != 1 && addressFamily != 2 {
|
||||||
|
conn.writeMessage(522, "Network protocol not supported, use (1,2)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
socket, err := newActiveSocket(host, port, conn.logger)
|
||||||
|
if err != nil {
|
||||||
|
conn.writeMessage(425, "Data connection failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.dataConn = socket
|
||||||
|
conn.writeMessage(200, "Connection established ("+strconv.Itoa(port)+")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandEpsv responds to the EPSV FTP command. It allows the client to
|
||||||
|
// request a passive data socket with more options than the original PASV
|
||||||
|
// command. It mainly adds ipv6 support, although we don't support that yet.
|
||||||
|
type commandEpsv struct{}
|
||||||
|
|
||||||
|
func (cmd commandEpsv) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandEpsv) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandEpsv) Execute(conn *ftpConn, param string) {
|
||||||
|
socket, err := newPassiveSocket(conn.logger)
|
||||||
|
if err != nil {
|
||||||
|
conn.writeMessage(425, "Data connection failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.dataConn = socket
|
||||||
|
msg := fmt.Sprintf("Entering Extended Passive Mode (|||%d|)", socket.Port())
|
||||||
|
conn.writeMessage(229, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandList responds to the LIST FTP command. It allows the client to retreive
|
||||||
|
// a detailed listing of the contents of a directory.
|
||||||
|
type commandList struct{}
|
||||||
|
|
||||||
|
func (cmd commandList) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandList) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandList) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.writeMessage(150, "Opening ASCII mode data connection for file list")
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
files := conn.driver.DirContents(path)
|
||||||
|
formatter := newListFormatter(files)
|
||||||
|
conn.sendOutofbandData(formatter.Detailed())
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandNlst responds to the NLST FTP command. It allows the client to
|
||||||
|
// retreive a list of filenames in the current directory.
|
||||||
|
type commandNlst struct{}
|
||||||
|
|
||||||
|
func (cmd commandNlst) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandNlst) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandNlst) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.writeMessage(150, "Opening ASCII mode data connection for file list")
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
files := conn.driver.DirContents(path)
|
||||||
|
formatter := newListFormatter(files)
|
||||||
|
conn.sendOutofbandData(formatter.Short())
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandMdtm responds to the MDTM FTP command. It allows the client to
|
||||||
|
// retreive the last modified time of a file.
|
||||||
|
type commandMdtm struct{}
|
||||||
|
|
||||||
|
func (cmd commandMdtm) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandMdtm) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandMdtm) Execute(conn *ftpConn, param string) {
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
time, err := conn.driver.ModifiedTime(path)
|
||||||
|
if err == nil {
|
||||||
|
conn.writeMessage(213, strftime.Format("%Y%m%d%H%M%S", time))
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(450, "File not available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandMkd responds to the MKD FTP command. It allows the client to create
|
||||||
|
// a new directory
|
||||||
|
type commandMkd struct{}
|
||||||
|
|
||||||
|
func (cmd commandMkd) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandMkd) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandMkd) Execute(conn *ftpConn, param string) {
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
if conn.driver.MakeDir(path) {
|
||||||
|
conn.writeMessage(257, "Directory created")
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(550, "Action not taken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmdMode responds to the MODE FTP command.
|
||||||
|
//
|
||||||
|
// the original FTP spec had various options for hosts to negotiate how data
|
||||||
|
// would be sent over the data socket, In reality these days (S)tream mode
|
||||||
|
// is all that is used for the mode - data is just streamed down the data
|
||||||
|
// socket unchanged.
|
||||||
|
type commandMode struct{}
|
||||||
|
|
||||||
|
func (cmd commandMode) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandMode) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandMode) Execute(conn *ftpConn, param string) {
|
||||||
|
if strings.ToUpper(param) == "S" {
|
||||||
|
conn.writeMessage(200, "OK")
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(504, "MODE is an obsolete command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmdNoop responds to the NOOP FTP command.
|
||||||
|
//
|
||||||
|
// This is essentially a ping from the client so we just respond with an
|
||||||
|
// basic 200 message.
|
||||||
|
type commandNoop struct{}
|
||||||
|
|
||||||
|
func (cmd commandNoop) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandNoop) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandNoop) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.writeMessage(200, "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandPass respond to the PASS FTP command by asking the driver if the
|
||||||
|
// supplied username and password are valid
|
||||||
|
type commandPass struct{}
|
||||||
|
|
||||||
|
func (cmd commandPass) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandPass) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandPass) Execute(conn *ftpConn, param string) {
|
||||||
|
info := conn.reqUser + "&&" + param
|
||||||
|
arr := strings.Split(conn.conn.RemoteAddr().String(), ":")
|
||||||
|
|
||||||
|
// 判断是否为 RPC 客户端
|
||||||
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("FTP", "", arr[0], info, "0")
|
||||||
|
} else {
|
||||||
|
go report.ReportFTP(arr[0], "本机", info)
|
||||||
|
}
|
||||||
|
|
||||||
|
if conn.driver.Authenticate(conn.reqUser, param) {
|
||||||
|
conn.user = conn.reqUser
|
||||||
|
conn.reqUser = ""
|
||||||
|
conn.writeMessage(230, "Password ok, continue")
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(530, "Incorrect password, not logged in")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandPasv responds to the PASV FTP command.
|
||||||
|
//
|
||||||
|
// The client is requesting us to open a new TCP listing socket and wait for them
|
||||||
|
// to connect to it.
|
||||||
|
type commandPasv struct{}
|
||||||
|
|
||||||
|
func (cmd commandPasv) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandPasv) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandPasv) Execute(conn *ftpConn, param string) {
|
||||||
|
socket, err := newPassiveSocket(conn.logger)
|
||||||
|
if err != nil {
|
||||||
|
conn.writeMessage(425, "Data connection failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.dataConn = socket
|
||||||
|
p1 := socket.Port() / 256
|
||||||
|
p2 := socket.Port() - (p1 * 256)
|
||||||
|
|
||||||
|
quads := strings.Split(socket.Host(), ".")
|
||||||
|
target := fmt.Sprintf("(%s,%s,%s,%s,%d,%d)", quads[0], quads[1], quads[2], quads[3], p1, p2)
|
||||||
|
msg := "Entering Passive Mode " + target
|
||||||
|
conn.writeMessage(227, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandPort responds to the PORT FTP command.
|
||||||
|
//
|
||||||
|
// The client has opened a listening socket for sending out of band data and
|
||||||
|
// is requesting that we connect to it
|
||||||
|
type commandPort struct{}
|
||||||
|
|
||||||
|
func (cmd commandPort) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandPort) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandPort) Execute(conn *ftpConn, param string) {
|
||||||
|
nums := strings.Split(param, ",")
|
||||||
|
portOne, _ := strconv.Atoi(nums[4])
|
||||||
|
portTwo, _ := strconv.Atoi(nums[5])
|
||||||
|
port := (portOne * 256) + portTwo
|
||||||
|
host := nums[0] + "." + nums[1] + "." + nums[2] + "." + nums[3]
|
||||||
|
socket, err := newActiveSocket(host, port, conn.logger)
|
||||||
|
if err != nil {
|
||||||
|
conn.writeMessage(425, "Data connection failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn.dataConn = socket
|
||||||
|
conn.writeMessage(200, "Connection established ("+strconv.Itoa(port)+")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandPwd responds to the PWD FTP command.
|
||||||
|
//
|
||||||
|
// Tells the client what the current working directory is.
|
||||||
|
type commandPwd struct{}
|
||||||
|
|
||||||
|
func (cmd commandPwd) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandPwd) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandPwd) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.writeMessage(257, "\""+conn.namePrefix+"\" is the current directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandQuit responds to the QUIT FTP command. The client has requested the
|
||||||
|
// connection be closed.
|
||||||
|
type commandQuit struct{}
|
||||||
|
|
||||||
|
func (cmd commandQuit) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandQuit) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandQuit) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandRetr responds to the RETR FTP command. It allows the client to
|
||||||
|
// download a file.
|
||||||
|
type commandRetr struct{}
|
||||||
|
|
||||||
|
func (cmd commandRetr) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandRetr) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandRetr) Execute(conn *ftpConn, param string) {
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
data, err := conn.driver.GetFile(path)
|
||||||
|
if err == nil {
|
||||||
|
bytes := strconv.Itoa(len(data))
|
||||||
|
conn.writeMessage(150, "Data transfer starting "+bytes+" bytes")
|
||||||
|
conn.sendOutofbandData(data)
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(551, "File not available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandRnfr responds to the RNFR FTP command. It's the first of two commands
|
||||||
|
// required for a client to rename a file.
|
||||||
|
type commandRnfr struct{}
|
||||||
|
|
||||||
|
func (cmd commandRnfr) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandRnfr) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandRnfr) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.renameFrom = conn.buildPath(param)
|
||||||
|
conn.writeMessage(350, "Requested file action pending further information.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmdRnto responds to the RNTO FTP command. It's the second of two commands
|
||||||
|
// required for a client to rename a file.
|
||||||
|
type commandRnto struct{}
|
||||||
|
|
||||||
|
func (cmd commandRnto) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandRnto) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandRnto) Execute(conn *ftpConn, param string) {
|
||||||
|
toPath := conn.buildPath(param)
|
||||||
|
if conn.driver.Rename(conn.renameFrom, toPath) {
|
||||||
|
conn.writeMessage(250, "File renamed")
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(550, "Action not taken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cmdRmd responds to the RMD FTP command. It allows the client to delete a
|
||||||
|
// directory.
|
||||||
|
type commandRmd struct{}
|
||||||
|
|
||||||
|
func (cmd commandRmd) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandRmd) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandRmd) Execute(conn *ftpConn, param string) {
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
if conn.driver.DeleteDir(path) {
|
||||||
|
conn.writeMessage(250, "Directory deleted")
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(550, "Action not taken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandSize responds to the SIZE FTP command. It returns the size of the
|
||||||
|
// requested path in bytes.
|
||||||
|
type commandSize struct{}
|
||||||
|
|
||||||
|
func (cmd commandSize) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandSize) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandSize) Execute(conn *ftpConn, param string) {
|
||||||
|
path := conn.buildPath(param)
|
||||||
|
bytes := conn.driver.Bytes(path)
|
||||||
|
if bytes >= 0 {
|
||||||
|
conn.writeMessage(213, strconv.Itoa(bytes))
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(450, "file not available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandStor responds to the STOR FTP command. It allows the user to upload a
|
||||||
|
// new file.
|
||||||
|
type commandStor struct{}
|
||||||
|
|
||||||
|
func (cmd commandStor) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandStor) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandStor) Execute(conn *ftpConn, param string) {
|
||||||
|
targetPath := conn.buildPath(param)
|
||||||
|
conn.writeMessage(150, "Data transfer starting")
|
||||||
|
tmpFile, err := ioutil.TempFile("", "stor")
|
||||||
|
if err != nil {
|
||||||
|
conn.writeMessage(450, "error during transfer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes, err := io.Copy(tmpFile, conn.dataConn)
|
||||||
|
if err != nil {
|
||||||
|
conn.writeMessage(450, "error during transfer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmpFile.Seek(0, 0)
|
||||||
|
uploadSuccess := conn.driver.PutFile(targetPath, tmpFile)
|
||||||
|
tmpFile.Close()
|
||||||
|
os.Remove(tmpFile.Name())
|
||||||
|
if uploadSuccess {
|
||||||
|
msg := "OK, received " + strconv.Itoa(int(bytes)) + " bytes"
|
||||||
|
conn.writeMessage(226, msg)
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(550, "Action not taken")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandStru responds to the STRU FTP command.
|
||||||
|
//
|
||||||
|
// like the MODE and TYPE commands, stru[cture] dates back to a time when the
|
||||||
|
// FTP protocol was more aware of the content of the files it was transferring,
|
||||||
|
// and would sometimes be expected to translate things like EOL markers on the
|
||||||
|
// fly.
|
||||||
|
//
|
||||||
|
// These days files are sent unmodified, and F(ile) mode is the only one we
|
||||||
|
// really need to support.
|
||||||
|
type commandStru struct{}
|
||||||
|
|
||||||
|
func (cmd commandStru) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandStru) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandStru) Execute(conn *ftpConn, param string) {
|
||||||
|
if strings.ToUpper(param) == "F" {
|
||||||
|
conn.writeMessage(200, "OK")
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(504, "STRU is an obsolete command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandSyst responds to the SYST FTP command by providing a canned response.
|
||||||
|
type commandSyst struct{}
|
||||||
|
|
||||||
|
func (cmd commandSyst) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandSyst) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandSyst) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.writeMessage(215, "UNIX Type: L8")
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandType responds to the TYPE FTP command.
|
||||||
|
//
|
||||||
|
// like the MODE and STRU commands, TYPE dates back to a time when the FTP
|
||||||
|
// protocol was more aware of the content of the files it was transferring, and
|
||||||
|
// would sometimes be expected to translate things like EOL markers on the fly.
|
||||||
|
//
|
||||||
|
// Valid options were A(SCII), I(mage), E(BCDIC) or LN (for local type). Since
|
||||||
|
// we plan to just accept bytes from the client unchanged, I think Image mode is
|
||||||
|
// adequate. The RFC requires we accept ASCII mode however, so accept it, but
|
||||||
|
// ignore it.
|
||||||
|
type commandType struct{}
|
||||||
|
|
||||||
|
func (cmd commandType) RequireParam() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandType) RequireAuth() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandType) Execute(conn *ftpConn, param string) {
|
||||||
|
if strings.ToUpper(param) == "A" {
|
||||||
|
conn.writeMessage(200, "Type set to ASCII")
|
||||||
|
} else if strings.ToUpper(param) == "I" {
|
||||||
|
conn.writeMessage(200, "Type set to binary")
|
||||||
|
} else {
|
||||||
|
conn.writeMessage(500, "Invalid type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// commandUser responds to the USER FTP command by asking for the password
|
||||||
|
type commandUser struct{}
|
||||||
|
|
||||||
|
func (cmd commandUser) RequireParam() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandUser) RequireAuth() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmd commandUser) Execute(conn *ftpConn, param string) {
|
||||||
|
conn.reqUser = param
|
||||||
|
conn.writeMessage(331, "User name ok, password required")
|
||||||
|
}
|
163
core/protocol/ftp/graval/ftpconn.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package graval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
welcomeMessage = "Welcome to the Go FTP Server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ftpConn struct {
|
||||||
|
conn *net.TCPConn
|
||||||
|
controlReader *bufio.Reader
|
||||||
|
controlWriter *bufio.Writer
|
||||||
|
dataConn ftpDataSocket
|
||||||
|
driver FTPDriver
|
||||||
|
logger *ftpLogger
|
||||||
|
sessionId string
|
||||||
|
namePrefix string
|
||||||
|
reqUser string
|
||||||
|
user string
|
||||||
|
renameFrom string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewftpConn constructs a new object that will handle the FTP protocol over
|
||||||
|
// an active net.TCPConn. The TCP connection should already be open before
|
||||||
|
// it is handed to this functions. driver is an instance of FTPDriver that
|
||||||
|
// will handle all auth and persistence details.
|
||||||
|
func newftpConn(tcpConn *net.TCPConn, driver FTPDriver) *ftpConn {
|
||||||
|
c := new(ftpConn)
|
||||||
|
c.namePrefix = "/"
|
||||||
|
c.conn = tcpConn
|
||||||
|
c.controlReader = bufio.NewReader(tcpConn)
|
||||||
|
c.controlWriter = bufio.NewWriter(tcpConn)
|
||||||
|
c.driver = driver
|
||||||
|
c.sessionId = newSessionId()
|
||||||
|
c.logger = newFtpLogger(c.sessionId)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a random 20 char string that can be used as a unique session ID
|
||||||
|
func newSessionId() string {
|
||||||
|
hash := sha256.New()
|
||||||
|
_, err := io.CopyN(hash, rand.Reader, 50)
|
||||||
|
if err != nil {
|
||||||
|
return "????????????????????"
|
||||||
|
}
|
||||||
|
md := hash.Sum(nil)
|
||||||
|
mdStr := hex.EncodeToString(md)
|
||||||
|
return mdStr[0:20]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve starts an endless loop that reads FTP commands from the client and
|
||||||
|
// responds appropriately. terminated is a channel that will receive a true
|
||||||
|
// message when the connection closes. This loop will be running inside a
|
||||||
|
// goroutine, so use this channel to be notified when the connection can be
|
||||||
|
// cleaned up.
|
||||||
|
func (ftpConn *ftpConn) Serve() {
|
||||||
|
ftpConn.logger.Print("Connection Established")
|
||||||
|
// send welcome
|
||||||
|
ftpConn.writeMessage(220, welcomeMessage)
|
||||||
|
// read commands
|
||||||
|
for {
|
||||||
|
line, err := ftpConn.controlReader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ftpConn.receiveLine(line)
|
||||||
|
}
|
||||||
|
ftpConn.logger.Print("Connection Terminated")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close will manually close this connection, even if the client isn't ready.
|
||||||
|
func (ftpConn *ftpConn) Close() {
|
||||||
|
ftpConn.conn.Close()
|
||||||
|
if ftpConn.dataConn != nil {
|
||||||
|
ftpConn.dataConn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// receiveLine accepts a single line FTP command and co-ordinates an
|
||||||
|
// appropriate response.
|
||||||
|
func (ftpConn *ftpConn) receiveLine(line string) {
|
||||||
|
command, param := ftpConn.parseLine(line)
|
||||||
|
ftpConn.logger.PrintCommand(command, param)
|
||||||
|
cmdObj := commands[command]
|
||||||
|
if cmdObj == nil {
|
||||||
|
ftpConn.writeMessage(500, "Command not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if cmdObj.RequireParam() && param == "" {
|
||||||
|
ftpConn.writeMessage(553, "action aborted, required param missing")
|
||||||
|
} else if cmdObj.RequireAuth() && ftpConn.user == "" {
|
||||||
|
ftpConn.writeMessage(530, "not logged in")
|
||||||
|
} else {
|
||||||
|
cmdObj.Execute(ftpConn, param)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ftpConn *ftpConn) parseLine(line string) (string, string) {
|
||||||
|
params := strings.SplitN(strings.Trim(line, "\r\n"), " ", 2)
|
||||||
|
if len(params) == 1 {
|
||||||
|
return params[0], ""
|
||||||
|
}
|
||||||
|
return params[0], strings.TrimSpace(params[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeMessage will send a standard FTP response back to the client.
|
||||||
|
func (ftpConn *ftpConn) writeMessage(code int, message string) (wrote int, err error) {
|
||||||
|
ftpConn.logger.PrintResponse(code, message)
|
||||||
|
line := fmt.Sprintf("%d %s\r\n", code, message)
|
||||||
|
wrote, err = ftpConn.controlWriter.WriteString(line)
|
||||||
|
ftpConn.controlWriter.Flush()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPath takes a client supplied path or filename and generates a safe
|
||||||
|
// absolute path within their account sandbox.
|
||||||
|
//
|
||||||
|
// buildpath("/")
|
||||||
|
// => "/"
|
||||||
|
// buildpath("one.txt")
|
||||||
|
// => "/one.txt"
|
||||||
|
// buildpath("/files/two.txt")
|
||||||
|
// => "/files/two.txt"
|
||||||
|
// buildpath("files/two.txt")
|
||||||
|
// => "files/two.txt"
|
||||||
|
// buildpath("/../../../../etc/passwd")
|
||||||
|
// => "/etc/passwd"
|
||||||
|
//
|
||||||
|
// The driver implementation is responsible for deciding how to treat this path.
|
||||||
|
// Obviously they MUST NOT just read the path off disk. The probably want to
|
||||||
|
// prefix the path with something to scope the users access to a sandbox.
|
||||||
|
func (ftpConn *ftpConn) buildPath(filename string) (fullPath string) {
|
||||||
|
if len(filename) > 0 && filename[0:1] == "/" {
|
||||||
|
fullPath = filepath.Clean(filename)
|
||||||
|
} else if len(filename) > 0 && filename != "-a" {
|
||||||
|
fullPath = filepath.Clean(ftpConn.namePrefix + "/" + filename)
|
||||||
|
} else {
|
||||||
|
fullPath = filepath.Clean(ftpConn.namePrefix)
|
||||||
|
}
|
||||||
|
fullPath = strings.Replace(fullPath, "//", "/", -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendOutofbandData will send a string to the client via the currently open
|
||||||
|
// data socket. Assumes the socket is open and ready to be used.
|
||||||
|
func (ftpConn *ftpConn) sendOutofbandData(data string) {
|
||||||
|
bytes := len(data)
|
||||||
|
ftpConn.dataConn.Write([]byte(data))
|
||||||
|
ftpConn.dataConn.Close()
|
||||||
|
message := "Closing data connection, sent " + strconv.Itoa(bytes) + " bytes"
|
||||||
|
ftpConn.writeMessage(226, message)
|
||||||
|
}
|
167
core/protocol/ftp/graval/ftpdatasocket.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package graval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A data socket is used to send non-control data between the client and
|
||||||
|
// server.
|
||||||
|
type ftpDataSocket interface {
|
||||||
|
Host() string
|
||||||
|
|
||||||
|
Port() int
|
||||||
|
|
||||||
|
// the standard io.Reader interface
|
||||||
|
Read(p []byte) (n int, err error)
|
||||||
|
|
||||||
|
// the standard io.Writer interface
|
||||||
|
Write(p []byte) (n int, err error)
|
||||||
|
|
||||||
|
// the standard io.Closer interface
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ftpActiveSocket struct {
|
||||||
|
conn *net.TCPConn
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
logger *ftpLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func newActiveSocket(host string, port int, logger *ftpLogger) (ftpDataSocket, error) {
|
||||||
|
connectTo := buildTcpString(host, port)
|
||||||
|
logger.Print("Opening active data connection to " + connectTo)
|
||||||
|
raddr, err := net.ResolveTCPAddr("tcp", connectTo)
|
||||||
|
if err != nil {
|
||||||
|
logger.Print(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tcpConn, err := net.DialTCP("tcp", nil, raddr)
|
||||||
|
if err != nil {
|
||||||
|
logger.Print(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
socket := new(ftpActiveSocket)
|
||||||
|
socket.conn = tcpConn
|
||||||
|
socket.host = host
|
||||||
|
socket.port = port
|
||||||
|
socket.logger = logger
|
||||||
|
return socket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpActiveSocket) Host() string {
|
||||||
|
return socket.host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpActiveSocket) Port() int {
|
||||||
|
return socket.port
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpActiveSocket) Read(p []byte) (n int, err error) {
|
||||||
|
return socket.conn.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpActiveSocket) Write(p []byte) (n int, err error) {
|
||||||
|
return socket.conn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpActiveSocket) Close() error {
|
||||||
|
return socket.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type ftpPassiveSocket struct {
|
||||||
|
conn *net.TCPConn
|
||||||
|
port int
|
||||||
|
ingress chan []byte
|
||||||
|
egress chan []byte
|
||||||
|
logger *ftpLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPassiveSocket(logger *ftpLogger) (ftpDataSocket, error) {
|
||||||
|
socket := new(ftpPassiveSocket)
|
||||||
|
socket.ingress = make(chan []byte)
|
||||||
|
socket.egress = make(chan []byte)
|
||||||
|
socket.logger = logger
|
||||||
|
go socket.ListenAndServe()
|
||||||
|
for {
|
||||||
|
if socket.Port() > 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
return socket, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpPassiveSocket) Host() string {
|
||||||
|
return "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpPassiveSocket) Port() int {
|
||||||
|
return socket.port
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpPassiveSocket) Read(p []byte) (n int, err error) {
|
||||||
|
if socket.waitForOpenSocket() == false {
|
||||||
|
return 0, errors.New("data socket unavailable")
|
||||||
|
}
|
||||||
|
return socket.conn.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpPassiveSocket) Write(p []byte) (n int, err error) {
|
||||||
|
if socket.waitForOpenSocket() == false {
|
||||||
|
return 0, errors.New("data socket unavailable")
|
||||||
|
}
|
||||||
|
return socket.conn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpPassiveSocket) Close() error {
|
||||||
|
socket.logger.Print("closing passive data socket")
|
||||||
|
return socket.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpPassiveSocket) ListenAndServe() {
|
||||||
|
laddr, err := net.ResolveTCPAddr("tcp", socket.Host()+":0")
|
||||||
|
if err != nil {
|
||||||
|
socket.logger.Print(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
listener, err := net.ListenTCP("tcp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
socket.logger.Print(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
add := listener.Addr()
|
||||||
|
parts := strings.Split(add.String(), ":")
|
||||||
|
port, err := strconv.Atoi(parts[1])
|
||||||
|
if err == nil {
|
||||||
|
socket.port = port
|
||||||
|
}
|
||||||
|
tcpConn, err := listener.AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
socket.logger.Print(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
socket.conn = tcpConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (socket *ftpPassiveSocket) waitForOpenSocket() bool {
|
||||||
|
retries := 0
|
||||||
|
for {
|
||||||
|
if socket.conn != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if retries > 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
socket.logger.Print("sleeping, socket isn't open")
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
retries += 1
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
67
core/protocol/ftp/graval/ftpdriver.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package graval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// For each client that connects to the server, a new FTPDriver is required.
|
||||||
|
// Create an implementation if this interface and provide it to FTPServer.
|
||||||
|
type FTPDriverFactory interface {
|
||||||
|
NewDriver() (FTPDriver, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// You will create an implementation of this interface that speaks to your
|
||||||
|
// chosen persistence layer. graval will create a new instance of your
|
||||||
|
// driver for each client that connects and delegate to it as required.
|
||||||
|
type FTPDriver interface {
|
||||||
|
// params - username, password
|
||||||
|
// returns - true if the provided details are valid
|
||||||
|
Authenticate(string, string) bool
|
||||||
|
|
||||||
|
// params - a file path
|
||||||
|
// returns - an int with the number of bytes in the file or -1 if the file
|
||||||
|
// doesn't exist
|
||||||
|
Bytes(string) int
|
||||||
|
|
||||||
|
// params - a file path
|
||||||
|
// returns - a time indicating when the requested path was last modified
|
||||||
|
// - an error if the file doesn't exist or the user lacks
|
||||||
|
// permissions
|
||||||
|
ModifiedTime(string) (time.Time, error)
|
||||||
|
|
||||||
|
// params - path
|
||||||
|
// returns - true if the current user is permitted to change to the
|
||||||
|
// requested path
|
||||||
|
ChangeDir(string) bool
|
||||||
|
|
||||||
|
// params - path
|
||||||
|
// returns - a collection of items describing the contents of the requested
|
||||||
|
// path
|
||||||
|
DirContents(string) []os.FileInfo
|
||||||
|
|
||||||
|
// params - path
|
||||||
|
// returns - true if the directory was deleted
|
||||||
|
DeleteDir(string) bool
|
||||||
|
|
||||||
|
// params - path
|
||||||
|
// returns - true if the file was deleted
|
||||||
|
DeleteFile(string) bool
|
||||||
|
|
||||||
|
// params - from_path, to_path
|
||||||
|
// returns - true if the file was renamed
|
||||||
|
Rename(string, string) bool
|
||||||
|
|
||||||
|
// params - path
|
||||||
|
// returns - true if the new directory was created
|
||||||
|
MakeDir(string) bool
|
||||||
|
|
||||||
|
// params - path
|
||||||
|
// returns - a string containing the file data to send to the client
|
||||||
|
GetFile(string) (string, error)
|
||||||
|
|
||||||
|
// params - desination path, an io.Reader containing the file data
|
||||||
|
// returns - true if the data was successfully persisted
|
||||||
|
PutFile(string, io.Reader) bool
|
||||||
|
}
|
58
core/protocol/ftp/graval/ftpfileinfo.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package graval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ftpFileInfo struct {
|
||||||
|
name string
|
||||||
|
bytes int64
|
||||||
|
mode os.FileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *ftpFileInfo) Name() string {
|
||||||
|
return info.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *ftpFileInfo) Size() int64 {
|
||||||
|
return info.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *ftpFileInfo) Mode() os.FileMode {
|
||||||
|
return info.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *ftpFileInfo) ModTime() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *ftpFileInfo) IsDir() bool {
|
||||||
|
return (info.mode | os.ModeDir) == os.ModeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info *ftpFileInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDirItem creates a new os.FileInfo that represents a single diretory. Use
|
||||||
|
// this function to build the response to DirContents() in your FTPDriver
|
||||||
|
// implementation.
|
||||||
|
func NewDirItem(name string) os.FileInfo {
|
||||||
|
d := new(ftpFileInfo)
|
||||||
|
d.name = name
|
||||||
|
d.bytes = int64(0)
|
||||||
|
d.mode = os.ModeDir | 666
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileItem creates a new os.FileInfo that represents a single file. Use
|
||||||
|
// this function to build the response to DirContents() in your FTPDriver
|
||||||
|
// implementation.
|
||||||
|
func NewFileItem(name string, bytes int) os.FileInfo {
|
||||||
|
f := new(ftpFileInfo)
|
||||||
|
f.name = name
|
||||||
|
f.bytes = int64(bytes)
|
||||||
|
f.mode = 666
|
||||||
|
return f
|
||||||
|
}
|
37
core/protocol/ftp/graval/ftplogger.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package graval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use an instance of this to log in a standard format
|
||||||
|
type ftpLogger struct {
|
||||||
|
sessionId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFtpLogger(id string) *ftpLogger {
|
||||||
|
l := new(ftpLogger)
|
||||||
|
l.sessionId = id
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *ftpLogger) Print(message interface{}) {
|
||||||
|
log.Printf("%s %s", logger.sessionId, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *ftpLogger) Printf(format string, v ...interface{}) {
|
||||||
|
logger.Print(fmt.Sprintf(format, v...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *ftpLogger) PrintCommand(command string, params string) {
|
||||||
|
if command == "PASS" {
|
||||||
|
log.Printf("%s > PASS ****", logger.sessionId)
|
||||||
|
} else {
|
||||||
|
log.Printf("%s > %s %s", logger.sessionId, command, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *ftpLogger) PrintResponse(code int, message string) {
|
||||||
|
log.Printf("%s < %d %s", logger.sessionId, code, message)
|
||||||
|
}
|
144
core/protocol/ftp/graval/ftpserver.go
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// An experimental FTP server framework. By providing a simple driver class that
|
||||||
|
// responds to a handful of methods you can have a complete FTP server.
|
||||||
|
//
|
||||||
|
// Some sample use cases include persisting data to an Amazon S3 bucket, a
|
||||||
|
// relational database, redis or memory.
|
||||||
|
//
|
||||||
|
// There is a sample in-memory driver available - see the documentation for the
|
||||||
|
// graval-mem package or the graval READEME for more details.
|
||||||
|
package graval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// serverOpts contains parameters for graval.NewFTPServer()
|
||||||
|
type FTPServerOpts struct {
|
||||||
|
// The factory that will be used to create a new FTPDriver instance for
|
||||||
|
// each client connection. This is a mandatory option.
|
||||||
|
Factory FTPDriverFactory
|
||||||
|
|
||||||
|
// The hostname that the FTP server should listen on. Optional, defaults to
|
||||||
|
// "::", which means all hostnames on ipv4 and ipv6.
|
||||||
|
Hostname string
|
||||||
|
|
||||||
|
// The port that the FTP should listen on. Optional, defaults to 3000. In
|
||||||
|
// a production environment you will probably want to change this to 21.
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
// FTPServer is the root of your FTP application. You should instantiate one
|
||||||
|
// of these and call ListenAndServe() to start accepting client connections.
|
||||||
|
//
|
||||||
|
// Always use the NewFTPServer() method to create a new FTPServer.
|
||||||
|
type FTPServer struct {
|
||||||
|
name string
|
||||||
|
listenTo string
|
||||||
|
driverFactory FTPDriverFactory
|
||||||
|
logger *ftpLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverOptsWithDefaults copies an FTPServerOpts struct into a new struct,
|
||||||
|
// then adds any default values that are missing and returns the new data.
|
||||||
|
func serverOptsWithDefaults(opts *FTPServerOpts) *FTPServerOpts {
|
||||||
|
var newOpts FTPServerOpts
|
||||||
|
if opts == nil {
|
||||||
|
opts = &FTPServerOpts{}
|
||||||
|
}
|
||||||
|
if opts.Hostname == "" {
|
||||||
|
newOpts.Hostname = "::"
|
||||||
|
} else {
|
||||||
|
newOpts.Hostname = opts.Hostname
|
||||||
|
}
|
||||||
|
if opts.Port == 0 {
|
||||||
|
newOpts.Port = 3000
|
||||||
|
} else {
|
||||||
|
newOpts.Port = opts.Port
|
||||||
|
}
|
||||||
|
newOpts.Factory = opts.Factory
|
||||||
|
|
||||||
|
return &newOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFTPServer initialises a new FTP server. Configuration options are provided
|
||||||
|
// via an instance of FTPServerOpts. Calling this function in your code will
|
||||||
|
// probably look something like this:
|
||||||
|
//
|
||||||
|
// factory := &MyDriverFactory{}
|
||||||
|
// server := graval.NewFTPServer(&graval.FTPServerOpts{ Factory: factory })
|
||||||
|
//
|
||||||
|
// or:
|
||||||
|
//
|
||||||
|
// factory := &MyDriverFactory{}
|
||||||
|
// opts := &graval.FTPServerOpts{
|
||||||
|
// Factory: factory,
|
||||||
|
// Port: 2000,
|
||||||
|
// Hostname: "127.0.0.1",
|
||||||
|
// }
|
||||||
|
// server := graval.NewFTPServer(opts)
|
||||||
|
//
|
||||||
|
func NewFTPServer(opts *FTPServerOpts) *FTPServer {
|
||||||
|
opts = serverOptsWithDefaults(opts)
|
||||||
|
s := new(FTPServer)
|
||||||
|
s.listenTo = buildTcpString(opts.Hostname, opts.Port)
|
||||||
|
s.name = "Go FTP Server"
|
||||||
|
s.driverFactory = opts.Factory
|
||||||
|
s.logger = newFtpLogger("")
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenAndServe asks a new FTPServer to begin accepting client connections. It
|
||||||
|
// accepts no arguments - all configuration is provided via the NewFTPServer
|
||||||
|
// function.
|
||||||
|
//
|
||||||
|
// If the server fails to start for any reason, an error will be returned. Common
|
||||||
|
// errors are trying to bind to a privileged port or something else is already
|
||||||
|
// listening on the same port.
|
||||||
|
//
|
||||||
|
func (ftpServer *FTPServer) ListenAndServe() error {
|
||||||
|
laddr, err := net.ResolveTCPAddr("tcp", ftpServer.listenTo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
listener, err := net.ListenTCP("tcp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
tcpConn, err := listener.AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
ftpServer.logger.Print("listening error")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := ftpServer.driverFactory.NewDriver()
|
||||||
|
if err != nil {
|
||||||
|
ftpServer.logger.Print("Error creating driver, aborting client connection")
|
||||||
|
} else {
|
||||||
|
ftpConn := newftpConn(tcpConn, driver)
|
||||||
|
go ftpConn.Serve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTcpString(hostname string, port int) (result string) {
|
||||||
|
if strings.Contains(hostname, ":") {
|
||||||
|
// ipv6
|
||||||
|
if port == 0 {
|
||||||
|
result = "["+hostname+"]"
|
||||||
|
} else {
|
||||||
|
result = "["+hostname +"]:" + strconv.Itoa(port)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ipv4
|
||||||
|
if port == 0 {
|
||||||
|
result = hostname
|
||||||
|
} else {
|
||||||
|
result = hostname +":" + strconv.Itoa(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
56
core/protocol/ftp/graval/listformatter.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package graval
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jehiah/go-strftime"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listFormatter struct {
|
||||||
|
files []os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListFormatter(files []os.FileInfo) *listFormatter {
|
||||||
|
f := new(listFormatter)
|
||||||
|
f.files = files
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short returns a string that lists the collection of files by name only,
|
||||||
|
// one per line
|
||||||
|
func (formatter *listFormatter) Short() string {
|
||||||
|
output := ""
|
||||||
|
for _, file := range formatter.files {
|
||||||
|
output += file.Name() + "\r\n"
|
||||||
|
}
|
||||||
|
output += "\r\n"
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detailed returns a string that lists the collection of files with extra
|
||||||
|
// detail, one per line
|
||||||
|
func (formatter *listFormatter) Detailed() string {
|
||||||
|
output := ""
|
||||||
|
for _, file := range formatter.files {
|
||||||
|
output += file.Mode().String()
|
||||||
|
output += " 1 owner group "
|
||||||
|
output += lpad(strconv.Itoa(int(file.Size())), 12)
|
||||||
|
output += " " + strftime.Format("%b %d %H:%M", file.ModTime())
|
||||||
|
output += " " + file.Name()
|
||||||
|
output += "\r\n"
|
||||||
|
}
|
||||||
|
output += "\r\n"
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
func lpad(input string, length int) (result string) {
|
||||||
|
if len(input) < length {
|
||||||
|
result = strings.Repeat(" ", length-len(input))+input
|
||||||
|
} else if len(input) == length {
|
||||||
|
result = input
|
||||||
|
} else {
|
||||||
|
result = input[0:length]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
53
core/protocol/httpx/http.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package httpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*http 正向代理*/
|
||||||
|
|
||||||
|
type Pxy struct{}
|
||||||
|
|
||||||
|
func (p *Pxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
fmt.Printf("Received request %s %s %s\n", req.Method, req.Host, req.RemoteAddr)
|
||||||
|
|
||||||
|
transport := http.DefaultTransport
|
||||||
|
|
||||||
|
// step 1
|
||||||
|
outReq := new(http.Request)
|
||||||
|
*outReq = *req // this only does shallow copies of maps
|
||||||
|
|
||||||
|
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||||
|
if prior, ok := outReq.Header["X-Forwarded-For"]; ok {
|
||||||
|
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||||
|
}
|
||||||
|
outReq.Header.Set("X-Forwarded-For", clientIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 2
|
||||||
|
res, err := transport.RoundTrip(outReq)
|
||||||
|
if err != nil {
|
||||||
|
rw.WriteHeader(http.StatusBadGateway)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 3
|
||||||
|
for key, value := range res.Header {
|
||||||
|
for _, v := range value {
|
||||||
|
rw.Header().Add(key, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.WriteHeader(res.StatusCode)
|
||||||
|
io.Copy(rw, res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(addr string) {
|
||||||
|
http.Handle("/", &Pxy{})
|
||||||
|
http.ListenAndServe(addr, nil)
|
||||||
|
}
|
@ -3,13 +3,16 @@ package mysql
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
"strings"
|
"strings"
|
||||||
"HFish/error"
|
"HFish/error"
|
||||||
"HFish/core/report"
|
"HFish/core/report"
|
||||||
|
"HFish/utils/try"
|
||||||
|
"HFish/utils/log"
|
||||||
|
"HFish/utils/is"
|
||||||
|
"HFish/core/rpc/client"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
//读取文件时每次读取的字节数
|
//读取文件时每次读取的字节数
|
||||||
@ -37,8 +40,6 @@ var fileNames []string
|
|||||||
var recordClient = make(map[string]int)
|
var recordClient = make(map[string]int)
|
||||||
|
|
||||||
func Start(addr string, files string) {
|
func Start(addr string, files string) {
|
||||||
fmt.Println("mysql启动...")
|
|
||||||
|
|
||||||
// 启动 Mysql 服务端
|
// 启动 Mysql 服务端
|
||||||
serverAddr, _ := net.ResolveTCPAddr("tcp", addr)
|
serverAddr, _ := net.ResolveTCPAddr("tcp", addr)
|
||||||
listener, _ := net.ListenTCP("tcp", serverAddr)
|
listener, _ := net.ListenTCP("tcp", serverAddr)
|
||||||
@ -74,40 +75,62 @@ func connectionClientHandler(conn net.Conn) {
|
|||||||
connFrom := conn.RemoteAddr().String()
|
connFrom := conn.RemoteAddr().String()
|
||||||
|
|
||||||
arr := strings.Split(connFrom, ":")
|
arr := strings.Split(connFrom, ":")
|
||||||
id := report.ReportMysql(arr[0], connFrom+" 已经连接")
|
|
||||||
|
|
||||||
var ibuf = make([]byte, bufLength)
|
// 判断是否为 RPC 客户端
|
||||||
//第一个包
|
var id string
|
||||||
_, err := conn.Write(GreetingData)
|
|
||||||
error.Check(err, "")
|
|
||||||
|
|
||||||
//第二个包
|
if is.Rpc() {
|
||||||
_, err = conn.Read(ibuf[0: bufLength-1])
|
id = client.ReportResult("MYSQL", "", arr[0], connFrom+" 已经连接", "0")
|
||||||
|
} else {
|
||||||
//判断是否有Can Use LOAD DATA LOCAL标志,如果有才支持读取文件
|
id = strconv.FormatInt(report.ReportMysql(arr[0], "本机", connFrom+" 已经连接"), 10)
|
||||||
if (uint8(ibuf[4]) & uint8(128)) == 0 {
|
|
||||||
_ = conn.Close()
|
|
||||||
log.Println("The client not support LOAD DATA LOCAL")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
//第三个包
|
|
||||||
_, err = conn.Write(OkData)
|
|
||||||
|
|
||||||
//第四个包
|
log.Pr("Mysql", arr[0], "已经连接")
|
||||||
_, err = conn.Read(ibuf[0: bufLength-1])
|
|
||||||
|
|
||||||
//这里根据客户端连接的次数来选择读取文件列表里面的第几个文件
|
try.Try(func() {
|
||||||
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
var ibuf = make([]byte, bufLength)
|
||||||
getFileData := []byte{byte(len(fileNames[recordClient[ip]]) + 1), 0x00, 0x00, 0x01, 0xfb}
|
|
||||||
getFileData = append(getFileData, fileNames[recordClient[ip]]...)
|
|
||||||
|
|
||||||
//第五个包
|
//第一个包
|
||||||
_, err = conn.Write(getFileData)
|
_, err := conn.Write(GreetingData)
|
||||||
getRequestContent(conn, id)
|
error.Check(err, "")
|
||||||
|
|
||||||
|
//第二个包
|
||||||
|
_, err = conn.Read(ibuf[0: bufLength-1])
|
||||||
|
|
||||||
|
//判断是否有Can Use LOAD DATA LOCAL标志,如果有才支持读取文件
|
||||||
|
if (uint8(ibuf[4]) & uint8(128)) == 0 {
|
||||||
|
_ = conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//第三个包
|
||||||
|
_, err = conn.Write(OkData)
|
||||||
|
|
||||||
|
//第四个包
|
||||||
|
_, err = conn.Read(ibuf[0: bufLength-1])
|
||||||
|
|
||||||
|
//这里根据客户端连接的次数来选择读取文件列表里面的第几个文件
|
||||||
|
ip, _, _ := net.SplitHostPort(conn.RemoteAddr().String())
|
||||||
|
getFileData := []byte{byte(len(fileNames[recordClient[ip]]) + 1), 0x00, 0x00, 0x01, 0xfb}
|
||||||
|
getFileData = append(getFileData, fileNames[recordClient[ip]]...)
|
||||||
|
|
||||||
|
//第五个包
|
||||||
|
_, err = conn.Write(getFileData)
|
||||||
|
getRequestContent(conn, id)
|
||||||
|
|
||||||
|
}).Catch(func() {
|
||||||
|
log.Pr("Mysql", arr[0], "该客户端正在使用扫描器扫描")
|
||||||
|
|
||||||
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("MYSQL", "", "", "&&该客户端正在使用扫描器扫描", id)
|
||||||
|
} else {
|
||||||
|
// 有扫描器扫描
|
||||||
|
go report.ReportUpdateMysql(id, "&&该客户端正在使用扫描器扫描")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取客户端传来的文件数据
|
//获取客户端传来的文件数据
|
||||||
func getRequestContent(conn net.Conn, id int64) {
|
func getRequestContent(conn net.Conn, id string) {
|
||||||
var content bytes.Buffer
|
var content bytes.Buffer
|
||||||
//先读取数据包长度,前面3字节
|
//先读取数据包长度,前面3字节
|
||||||
lengthBuf := make([]byte, 3)
|
lengthBuf := make([]byte, 3)
|
||||||
@ -116,7 +139,6 @@ func getRequestContent(conn net.Conn, id int64) {
|
|||||||
|
|
||||||
totalDataLength := int(binary.LittleEndian.Uint32(append(lengthBuf, 0)))
|
totalDataLength := int(binary.LittleEndian.Uint32(append(lengthBuf, 0)))
|
||||||
if totalDataLength == 0 {
|
if totalDataLength == 0 {
|
||||||
log.Println("Get no file and closed connection.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
//然后丢掉1字节的序列号
|
//然后丢掉1字节的序列号
|
||||||
@ -128,7 +150,6 @@ func getRequestContent(conn net.Conn, id int64) {
|
|||||||
length, err := conn.Read(ibuf)
|
length, err := conn.Read(ibuf)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
log.Println("Get file and reading...")
|
|
||||||
//如果本次读取的内容长度+之前读取的内容长度大于文件内容总长度,则本次读取的文件内容只能留下一部分
|
//如果本次读取的内容长度+之前读取的内容长度大于文件内容总长度,则本次读取的文件内容只能留下一部分
|
||||||
if length+totalReadLength > totalDataLength {
|
if length+totalReadLength > totalDataLength {
|
||||||
length = totalDataLength - totalReadLength
|
length = totalDataLength - totalReadLength
|
||||||
@ -144,13 +165,18 @@ func getRequestContent(conn net.Conn, id int64) {
|
|||||||
case syscall.EAGAIN: // try again
|
case syscall.EAGAIN: // try again
|
||||||
continue
|
continue
|
||||||
default:
|
default:
|
||||||
log.Println("Closed connection: ", conn.RemoteAddr().String())
|
arr := strings.Split(conn.RemoteAddr().String(), ":")
|
||||||
|
log.Pr("Mysql", arr[0], "已经关闭连接")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//保存文件
|
// 获取文件内容
|
||||||
func getFileContent(content bytes.Buffer, id int64) {
|
func getFileContent(content bytes.Buffer, id string) {
|
||||||
report.ReportUpdateMysql(id, "&&"+content.String())
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("MYSQL", "", "", "&&"+content.String(), id)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateMysql(id, "&&"+content.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"HFish/utils/try"
|
"HFish/utils/try"
|
||||||
"HFish/core/report"
|
"HFish/core/report"
|
||||||
|
"HFish/utils/log"
|
||||||
|
"HFish/utils/is"
|
||||||
|
"HFish/core/rpc/client"
|
||||||
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var kvData map[string]string
|
var kvData map[string]string
|
||||||
@ -25,20 +29,37 @@ func Start(addr string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
arr := strings.Split(conn.RemoteAddr().String(), ":")
|
arr := strings.Split(conn.RemoteAddr().String(), ":")
|
||||||
id := report.ReportRedis(arr[0], conn.RemoteAddr().String()+" 已经连接")
|
|
||||||
|
// 判断是否为 RPC 客户端
|
||||||
|
var id string
|
||||||
|
|
||||||
|
if is.Rpc() {
|
||||||
|
id = client.ReportResult("REDIS", "", arr[0], conn.RemoteAddr().String()+" 已经连接", "0")
|
||||||
|
} else {
|
||||||
|
id = strconv.FormatInt(report.ReportRedis(arr[0], "本机", conn.RemoteAddr().String()+" 已经连接"), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Pr("Redis", arr[0], "已经连接")
|
||||||
|
|
||||||
go handleConnection(conn, id)
|
go handleConnection(conn, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//处理 Redis 连接
|
//处理 Redis 连接
|
||||||
func handleConnection(conn net.Conn, id int64) {
|
func handleConnection(conn net.Conn, id string) {
|
||||||
|
|
||||||
|
fmt.Println("redis ", id)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
str := parseRESP(conn)
|
str := parseRESP(conn)
|
||||||
|
|
||||||
switch value := str.(type) {
|
switch value := str.(type) {
|
||||||
case string:
|
case string:
|
||||||
report.ReportUpdateRedis(id, "&&"+str.(string))
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("REDIS", "", "", "&&"+str.(string), id)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateRedis(id, "&&"+str.(string))
|
||||||
|
}
|
||||||
|
|
||||||
if len(value) == 0 {
|
if len(value) == 0 {
|
||||||
goto end
|
goto end
|
||||||
@ -53,7 +74,11 @@ func handleConnection(conn net.Conn, id int64) {
|
|||||||
val := string(value[2])
|
val := string(value[2])
|
||||||
kvData[key] = val
|
kvData[key] = val
|
||||||
|
|
||||||
report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1]+" "+value[2])
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("REDIS", "", "", "&&"+value[0]+" "+value[1]+" "+value[2], id)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1]+" "+value[2])
|
||||||
|
}
|
||||||
|
|
||||||
}).Catch(func() {
|
}).Catch(func() {
|
||||||
// 取不到 key 会异常
|
// 取不到 key 会异常
|
||||||
@ -61,21 +86,37 @@ func handleConnection(conn net.Conn, id int64) {
|
|||||||
|
|
||||||
conn.Write([]byte("+OK\r\n"))
|
conn.Write([]byte("+OK\r\n"))
|
||||||
} else if value[0] == "GET" || value[0] == "get" {
|
} else if value[0] == "GET" || value[0] == "get" {
|
||||||
// 模拟 redis get
|
try.Try(func() {
|
||||||
key := string(value[1])
|
// 模拟 redis get
|
||||||
val := string(kvData[key])
|
key := string(value[1])
|
||||||
|
val := string(kvData[key])
|
||||||
|
|
||||||
valLen := strconv.Itoa(len(val))
|
valLen := strconv.Itoa(len(val))
|
||||||
str := "$" + valLen + "\r\n" + val + "\r\n"
|
str := "$" + valLen + "\r\n" + val + "\r\n"
|
||||||
|
|
||||||
report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1])
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("REDIS", "", "", "&&"+value[0]+" "+value[1], id)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1])
|
||||||
|
}
|
||||||
|
|
||||||
conn.Write([]byte(str))
|
conn.Write([]byte(str))
|
||||||
|
}).Catch(func() {
|
||||||
|
conn.Write([]byte("+OK\r\n"))
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
try.Try(func() {
|
try.Try(func() {
|
||||||
report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1])
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("REDIS", "", "", "&&"+value[0]+" "+value[1], id)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateRedis(id, "&&"+value[0]+" "+value[1])
|
||||||
|
}
|
||||||
}).Catch(func() {
|
}).Catch(func() {
|
||||||
report.ReportUpdateRedis(id, "&&"+value[0])
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("REDIS", "", "", "&&"+value[0], id)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateRedis(id, "&&"+value[0])
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
conn.Write([]byte("+OK\r\n"))
|
conn.Write([]byte("+OK\r\n"))
|
||||||
|
@ -4,6 +4,9 @@ import (
|
|||||||
"github.com/gliderlabs/ssh"
|
"github.com/gliderlabs/ssh"
|
||||||
"HFish/core/report"
|
"HFish/core/report"
|
||||||
"strings"
|
"strings"
|
||||||
|
"HFish/utils/log"
|
||||||
|
"HFish/utils/is"
|
||||||
|
"HFish/core/rpc/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(addr string) {
|
func Start(addr string) {
|
||||||
@ -12,7 +15,15 @@ func Start(addr string) {
|
|||||||
info := s.User() + "&&" + password
|
info := s.User() + "&&" + password
|
||||||
|
|
||||||
arr := strings.Split(s.RemoteAddr().String(), ":")
|
arr := strings.Split(s.RemoteAddr().String(), ":")
|
||||||
report.ReportSSH(arr[0], info)
|
|
||||||
|
log.Pr("SSH", arr[0], "已经连接")
|
||||||
|
|
||||||
|
// 判断是否为 RPC 客户端
|
||||||
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("SSH", "", arr[0], info, "0")
|
||||||
|
} else {
|
||||||
|
go report.ReportSSH(arr[0], "本机", info)
|
||||||
|
}
|
||||||
|
|
||||||
return false // false 代表 账号密码 不正确
|
return false // false 代表 账号密码 不正确
|
||||||
}),
|
}),
|
||||||
|
119
core/protocol/telnet/telnet.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package telnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"fmt"
|
||||||
|
"bufio"
|
||||||
|
"strings"
|
||||||
|
"os"
|
||||||
|
"HFish/utils/is"
|
||||||
|
"HFish/core/rpc/client"
|
||||||
|
"strconv"
|
||||||
|
"HFish/core/report"
|
||||||
|
"HFish/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 服务端连接
|
||||||
|
func server(address string, exitChan chan int) {
|
||||||
|
l, err := net.Listen("tcp", address)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
exitChan <- 1
|
||||||
|
}
|
||||||
|
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := strings.Split(conn.RemoteAddr().String(), ":")
|
||||||
|
|
||||||
|
// 判断是否为 RPC 客户端
|
||||||
|
var id string
|
||||||
|
|
||||||
|
if is.Rpc() {
|
||||||
|
id = client.ReportResult("TELNET", "", arr[0], conn.RemoteAddr().String()+" 已经连接", "0")
|
||||||
|
} else {
|
||||||
|
id = strconv.FormatInt(report.ReportTelnet(arr[0], "本机", conn.RemoteAddr().String()+" 已经连接"), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Pr("Telnet", arr[0], "已经连接")
|
||||||
|
|
||||||
|
// 根据连接开启会话, 这个过程需要并行执行
|
||||||
|
go handleSession(conn, exitChan, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 会话处理
|
||||||
|
func handleSession(conn net.Conn, exitChan chan int, id string) {
|
||||||
|
fmt.Println("Session started")
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
|
||||||
|
for {
|
||||||
|
str, err := reader.ReadString('\n')
|
||||||
|
|
||||||
|
// telnet命令
|
||||||
|
if err == nil {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
|
||||||
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("TELNET", "", "", "&&"+str, id)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateRedis(id, "&&"+str)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !processTelnetCommand(str, exitChan) {
|
||||||
|
conn.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Write([]byte("OK" + "\r\n"))
|
||||||
|
} else {
|
||||||
|
// 发生错误
|
||||||
|
fmt.Println("Session closed")
|
||||||
|
conn.Close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// telent协议命令
|
||||||
|
func processTelnetCommand(str string, exitChan chan int) bool {
|
||||||
|
// @close指令表示终止本次会话
|
||||||
|
if strings.HasPrefix(str, "@close") {
|
||||||
|
fmt.Println("Session closed")
|
||||||
|
// 告知外部需要断开连接
|
||||||
|
return false
|
||||||
|
// @shutdown指令表示终止服务进程
|
||||||
|
} else if strings.HasPrefix(str, "@shutdown") {
|
||||||
|
fmt.Println("Server shutdown")
|
||||||
|
// 往通道中写入0, 阻塞等待接收方处理
|
||||||
|
exitChan <- 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打印输入的字符串
|
||||||
|
fmt.Println(str)
|
||||||
|
return true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(addr string) {
|
||||||
|
// 创建一个程序结束码的通道
|
||||||
|
exitChan := make(chan int)
|
||||||
|
|
||||||
|
// 将服务器并发运行
|
||||||
|
go server(addr, exitChan)
|
||||||
|
|
||||||
|
// 通道阻塞,等待接受返回值
|
||||||
|
code := <-exitChan
|
||||||
|
|
||||||
|
// 标记程序返回值并退出
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
@ -3,40 +3,142 @@ package report
|
|||||||
import (
|
import (
|
||||||
"HFish/core/dbUtil"
|
"HFish/core/dbUtil"
|
||||||
"time"
|
"time"
|
||||||
|
"HFish/utils/ip"
|
||||||
|
"strings"
|
||||||
|
"HFish/utils/send"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func alert(title string, agent string, ipx string, infox string) {
|
||||||
|
sql := `select status,info from hfish_setting where type = "alertMail"`
|
||||||
|
isAlertStatus := dbUtil.Query(sql)
|
||||||
|
|
||||||
|
status := strconv.FormatInt(isAlertStatus[0]["status"].(int64), 10)
|
||||||
|
|
||||||
|
// 判断是否启用通知
|
||||||
|
if status == "1" {
|
||||||
|
info := isAlertStatus[0]["info"]
|
||||||
|
config := strings.Split(info.(string), "&&")
|
||||||
|
|
||||||
|
text := `
|
||||||
|
<div><b>Hi,上钩了!</b></div>
|
||||||
|
<div><b><br /></b></div>
|
||||||
|
<div><b>集群名称:</b>` + agent + `</div>
|
||||||
|
<div><b>攻击IP:</b>` + ipx + `</div>
|
||||||
|
<div><b>上钩内容:</b>` + infox + `</div>
|
||||||
|
<div><br /></div>
|
||||||
|
<div><span style="color: rgb(128, 128, 128); font-size: 10px;">(HFish 自动发送)</span></div>
|
||||||
|
`
|
||||||
|
|
||||||
|
send.SendMail(config[4:], "[HFish]提醒你,"+title+"有鱼上钩!", text, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报 集群 状态
|
||||||
|
func ReportAgentStatus(agentName string, agentIp string, webStatus string, deepStatus string, sshStatus string, redisStatus string, mysqlStatus string, httpStatus string, telnetStatus string, ftpStatus string) {
|
||||||
|
sql := `
|
||||||
|
INSERT INTO hfish_colony (
|
||||||
|
agent_name,
|
||||||
|
agent_ip,
|
||||||
|
web_status,
|
||||||
|
deep_status,
|
||||||
|
ssh_status,
|
||||||
|
redis_status,
|
||||||
|
mysql_status,
|
||||||
|
http_status,
|
||||||
|
telnet_status,
|
||||||
|
ftp_status,
|
||||||
|
last_update_time
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(?,?,?,?,?,?,?,?,?,?,?);
|
||||||
|
`
|
||||||
|
|
||||||
|
id := dbUtil.Insert(sql, agentName, agentIp, webStatus, deepStatus, sshStatus, redisStatus, mysqlStatus, httpStatus, telnetStatus, ftpStatus, time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
|
||||||
|
// 如果 ID 等于0 代表 该数据以及存在
|
||||||
|
if id == 0 {
|
||||||
|
sql := `
|
||||||
|
UPDATE hfish_colony
|
||||||
|
SET agent_ip = ?, web_status = ?, deep_status = ?, ssh_status = ?, redis_status = ?, mysql_status = ?, http_status = ?, telnet_status = ?, ftp_status = ?, last_update_time =?
|
||||||
|
WHERE
|
||||||
|
agent_name =?;
|
||||||
|
`
|
||||||
|
|
||||||
|
dbUtil.Update(sql, agentIp, webStatus, deepStatus, sshStatus, redisStatus, mysqlStatus, httpStatus, telnetStatus, ftpStatus, time.Now().Format("2006-01-02 15:04:05"), agentName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 上报 WEB
|
// 上报 WEB
|
||||||
func ReportWeb(projectName string, ip string, info string) {
|
func ReportWeb(projectName string, agent string, ipx string, info string) {
|
||||||
sql := `INSERT INTO hfish_info(type,project_name,ip,info,create_time) values(?,?,?,?,?);`
|
ipInfo := ip.Get(ipx)
|
||||||
dbUtil.Insert(sql, "WEB", projectName, ip, info, time.Now().Format("2006-01-02 15:04:05"))
|
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||||
|
dbUtil.Insert(sql, "WEB", projectName, agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
go alert("WEB", agent, ipx, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报 暗网 WEB
|
||||||
|
func ReportDeepWeb(projectName string, agent string, ipx string, info string) {
|
||||||
|
ipInfo := ip.Get(ipx)
|
||||||
|
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||||
|
dbUtil.Insert(sql, "DEEP", projectName, agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
go alert("DEEP", agent, ipx, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上报 SSH
|
// 上报 SSH
|
||||||
func ReportSSH(ip string, info string) {
|
func ReportSSH(ipx string, agent string, info string) {
|
||||||
sql := `INSERT INTO hfish_info(type,project_name,ip,info,create_time) values(?,?,?,?,?);`
|
ipInfo := ip.Get(ipx)
|
||||||
dbUtil.Insert(sql, "SSH", "SSH钓鱼", ip, info, time.Now().Format("2006-01-02 15:04:05"))
|
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||||
|
dbUtil.Insert(sql, "SSH", "SSH蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
go alert("SSH", agent, ipx, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上报 Redis
|
// 上报 Redis
|
||||||
func ReportRedis(ip string, info string) int64 {
|
func ReportRedis(ipx string, agent string, info string) int64 {
|
||||||
sql := `INSERT INTO hfish_info(type,project_name,ip,info,create_time) values(?,?,?,?,?);`
|
ipInfo := ip.Get(ipx)
|
||||||
return dbUtil.Insert(sql, "REDIS", "Redis钓鱼", ip, info, time.Now().Format("2006-01-02 15:04:05"))
|
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||||
|
go alert("REDIS", agent, ipx, info)
|
||||||
|
return dbUtil.Insert(sql, "REDIS", "Redis蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 Redis 操作
|
// 更新 Redis 操作
|
||||||
func ReportUpdateRedis(id int64, info string) {
|
func ReportUpdateRedis(id string, info string) {
|
||||||
sql := `UPDATE hfish_info SET info = info||? WHERE id = ?;`
|
sql := `UPDATE hfish_info SET info = info||? WHERE id = ?;`
|
||||||
dbUtil.Update(sql, info, id)
|
dbUtil.Update(sql, info, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上报 Mysql
|
// 上报 Mysql
|
||||||
func ReportMysql(ip string, info string) int64 {
|
func ReportMysql(ipx string, agent string, info string) int64 {
|
||||||
sql := `INSERT INTO hfish_info(type,project_name,ip,info,create_time) values(?,?,?,?,?);`
|
ipInfo := ip.Get(ipx)
|
||||||
return dbUtil.Insert(sql, "MYSQL", "Mysql钓鱼", ip, info, time.Now().Format("2006-01-02 15:04:05"))
|
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||||
|
go alert("MYSQL", agent, ipx, info)
|
||||||
|
return dbUtil.Insert(sql, "MYSQL", "Mysql蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新 Redis 操作
|
// 更新 Mysql 操作
|
||||||
func ReportUpdateMysql(id int64, info string) {
|
func ReportUpdateMysql(id string, info string) {
|
||||||
|
sql := `UPDATE hfish_info SET info = info||? WHERE id = ?;`
|
||||||
|
dbUtil.Update(sql, info, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报 FTP
|
||||||
|
func ReportFTP(ipx string, agent string, info string) {
|
||||||
|
ipInfo := ip.Get(ipx)
|
||||||
|
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||||
|
dbUtil.Insert(sql, "FTP", "FTP蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
go alert("FTP", agent, ipx, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报 Telnet
|
||||||
|
func ReportTelnet(ipx string, agent string, info string) int64 {
|
||||||
|
ipInfo := ip.Get(ipx)
|
||||||
|
sql := `INSERT INTO hfish_info(type,project_name,agent,ip,ip_info,info,create_time) values(?,?,?,?,?,?,?);`
|
||||||
|
go alert("TELNET", agent, ipx, info)
|
||||||
|
return dbUtil.Insert(sql, "TELNET", "Telnet蜜罐", agent, ipx, ipInfo, info, time.Now().Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 Telnet 操作
|
||||||
|
func ReportUpdateTelnet(id string, info string) {
|
||||||
sql := `UPDATE hfish_info SET info = info||? WHERE id = ?;`
|
sql := `UPDATE hfish_info SET info = info||? WHERE id = ?;`
|
||||||
dbUtil.Update(sql, info, id)
|
dbUtil.Update(sql, info, id)
|
||||||
}
|
}
|
||||||
|
103
core/rpc/client/client.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/rpc"
|
||||||
|
"HFish/utils/log"
|
||||||
|
"HFish/utils/conf"
|
||||||
|
"HFish/utils/ip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 上报状态结构
|
||||||
|
type Status struct {
|
||||||
|
AgentIp string
|
||||||
|
AgentName string
|
||||||
|
Web, Deep, Ssh, Redis, Mysql, Http, Telnet, Ftp string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报结果结构
|
||||||
|
type Result struct {
|
||||||
|
AgentIp string
|
||||||
|
AgentName string
|
||||||
|
Type string
|
||||||
|
ProjectName string
|
||||||
|
SourceIp string
|
||||||
|
Info string
|
||||||
|
Id string // 数据库ID,更新用 0 为新插入数据
|
||||||
|
}
|
||||||
|
|
||||||
|
func createClient() (*rpc.Client, bool) {
|
||||||
|
rpcAddr := conf.Get("rpc", "addr")
|
||||||
|
client, err := rpc.Dial("tcp", rpcAddr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Pr("RPC", "127.0.0.1", "连接 RPC Server 失败")
|
||||||
|
return client, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func reportStatus(rpcName string, ftpStatus string, telnetStatus string, httpStatus string, mysqlStatus string, redisStatus string, sshStatus string, webStatus string, darkStatus string) {
|
||||||
|
client, boolStatus := createClient()
|
||||||
|
|
||||||
|
if boolStatus {
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
status := Status{
|
||||||
|
ip.GetLocalIp(),
|
||||||
|
rpcName,
|
||||||
|
webStatus,
|
||||||
|
darkStatus,
|
||||||
|
sshStatus,
|
||||||
|
redisStatus,
|
||||||
|
mysqlStatus,
|
||||||
|
httpStatus,
|
||||||
|
telnetStatus,
|
||||||
|
ftpStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply string
|
||||||
|
err := client.Call("HFishRPCService.ReportStatus", status, &reply)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Pr("RPC", "127.0.0.1", "上报服务状态失败", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReportResult(typex string, projectName string, sourceIp string, info string, id string) string {
|
||||||
|
// projectName 只有 WEB 才需要传项目名 其他协议空即可
|
||||||
|
// id 0 为 新插入数据,非 0 都是更新数据
|
||||||
|
// id 非 0 的时候 sourceIp 为空
|
||||||
|
client, boolStatus := createClient()
|
||||||
|
|
||||||
|
if boolStatus {
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
rpcName := conf.Get("rpc", "name")
|
||||||
|
|
||||||
|
result := Result{
|
||||||
|
ip.GetLocalIp(),
|
||||||
|
rpcName,
|
||||||
|
typex,
|
||||||
|
projectName,
|
||||||
|
sourceIp,
|
||||||
|
info,
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply string
|
||||||
|
err := client.Call("HFishRPCService.ReportResult", result, &reply)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Pr("RPC", "127.0.0.1", "上报上钩结果失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(rpcName string, ftpStatus string, telnetStatus string, httpStatus string, mysqlStatus string, redisStatus string, sshStatus string, webStatus string, darkStatus string) {
|
||||||
|
reportStatus(rpcName, ftpStatus, telnetStatus, httpStatus, mysqlStatus, redisStatus, sshStatus, webStatus, darkStatus)
|
||||||
|
}
|
113
core/rpc/server/server.go
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/rpc"
|
||||||
|
"net"
|
||||||
|
"HFish/utils/log"
|
||||||
|
"HFish/core/report"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 上报状态结构
|
||||||
|
type Status struct {
|
||||||
|
AgentIp string
|
||||||
|
AgentName string
|
||||||
|
Web, Deep, Ssh, Redis, Mysql, Http, Telnet, Ftp string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报结果结构
|
||||||
|
type Result struct {
|
||||||
|
AgentIp string
|
||||||
|
AgentName string
|
||||||
|
Type string
|
||||||
|
ProjectName string
|
||||||
|
SourceIp string
|
||||||
|
Info string
|
||||||
|
Id string // 数据库ID,更新用 0 为新插入数据
|
||||||
|
}
|
||||||
|
|
||||||
|
type HFishRPCService int
|
||||||
|
|
||||||
|
// 上报状态 RPC 方法
|
||||||
|
func (t *HFishRPCService) ReportStatus(s *Status, reply *string) error {
|
||||||
|
|
||||||
|
// 上报 客户端 状态
|
||||||
|
go report.ReportAgentStatus(
|
||||||
|
s.AgentName,
|
||||||
|
s.AgentIp,
|
||||||
|
s.Web,
|
||||||
|
s.Deep,
|
||||||
|
s.Ssh,
|
||||||
|
s.Redis,
|
||||||
|
s.Mysql,
|
||||||
|
s.Http,
|
||||||
|
s.Telnet,
|
||||||
|
s.Ftp,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上报结果 RPC 方法
|
||||||
|
func (t *HFishRPCService) ReportResult(r *Result, reply *string) error {
|
||||||
|
var idx string
|
||||||
|
|
||||||
|
switch r.Type {
|
||||||
|
case "WEB":
|
||||||
|
go report.ReportWeb(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||||
|
case "DEEP":
|
||||||
|
go report.ReportDeepWeb(r.ProjectName, r.AgentName, r.SourceIp, r.Info)
|
||||||
|
case "SSH":
|
||||||
|
go report.ReportSSH(r.SourceIp, r.AgentName, r.Info)
|
||||||
|
case "REDIS":
|
||||||
|
if r.Id == "0" {
|
||||||
|
id := report.ReportRedis(r.SourceIp, r.AgentName, r.Info)
|
||||||
|
idx = strconv.FormatInt(id, 10)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateRedis(r.Id, r.Info)
|
||||||
|
}
|
||||||
|
case "MYSQL":
|
||||||
|
if r.Id == "0" {
|
||||||
|
id := report.ReportMysql(r.SourceIp, r.AgentName, r.Info)
|
||||||
|
idx = strconv.FormatInt(id, 10)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateMysql(r.Id, r.Info)
|
||||||
|
}
|
||||||
|
case "TELNET":
|
||||||
|
if r.Id == "0" {
|
||||||
|
id := report.ReportTelnet(r.SourceIp, r.AgentName, r.Info)
|
||||||
|
idx = strconv.FormatInt(id, 10)
|
||||||
|
} else {
|
||||||
|
go report.ReportUpdateTelnet(r.Id, r.Info)
|
||||||
|
}
|
||||||
|
case "FTP":
|
||||||
|
go report.ReportFTP(r.SourceIp, r.AgentName, r.Info)
|
||||||
|
}
|
||||||
|
|
||||||
|
*reply = idx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动 RPC 服务端
|
||||||
|
func Start(addr string) {
|
||||||
|
rpcService := new(HFishRPCService)
|
||||||
|
rpc.Register(rpcService)
|
||||||
|
|
||||||
|
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Pr("RPC", "127.0.0.1", "RPC Server 启动失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Pr("RPC", "127.0.0.1", "RPC Server 监听地址失败", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rpc.ServeConn(conn)
|
||||||
|
}
|
||||||
|
}
|
BIN
db/hfish.db
@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
func Check(e error, tips string) {
|
func Check(e error, tips string) {
|
||||||
if e != nil {
|
if e != nil {
|
||||||
panic(e)
|
|
||||||
fmt.Println(tips)
|
fmt.Println(tips)
|
||||||
|
panic(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,3 +48,10 @@ func ErrLoginFail() map[string]interface{} {
|
|||||||
"msg": "账号密码不正确",
|
"msg": "账号密码不正确",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrEmailFail() map[string]interface{} {
|
||||||
|
return gin.H{
|
||||||
|
"code": 1003,
|
||||||
|
"msg": "邮箱未启用",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
2
go.mod
@ -4,9 +4,11 @@ go 1.12
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
|
||||||
|
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/gin-gonic/gin v1.4.0
|
github.com/gin-gonic/gin v1.4.0
|
||||||
github.com/gliderlabs/ssh v0.2.2
|
github.com/gliderlabs/ssh v0.2.2
|
||||||
|
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869
|
||||||
github.com/mattn/go-sqlite3 v1.11.0
|
github.com/mattn/go-sqlite3 v1.11.0
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
|
5
go.sum
@ -1,5 +1,7 @@
|
|||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 h1:OYA+5W64v3OgClL+IrOD63t4i/RW7RqrAVl9LTZ9UqQ=
|
||||||
|
github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||||
@ -12,6 +14,8 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
|||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4=
|
||||||
|
github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag=
|
||||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||||
@ -36,6 +40,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
|||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
|
BIN
images/1.png
Before Width: | Height: | Size: 782 KiB |
BIN
images/2.png
Before Width: | Height: | Size: 776 KiB |
BIN
images/3.png
Before Width: | Height: | Size: 1.0 MiB |
BIN
images/colony.png
Normal file
After Width: | Height: | Size: 528 KiB |
BIN
images/dashboard.png
Normal file
After Width: | Height: | Size: 812 KiB |
BIN
images/fish.png
Normal file
After Width: | Height: | Size: 899 KiB |
BIN
images/ftp.png
Normal file
After Width: | Height: | Size: 772 KiB |
BIN
images/login.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
images/mail.png
Normal file
After Width: | Height: | Size: 761 KiB |
BIN
images/ssh.png
Normal file
After Width: | Height: | Size: 815 KiB |
BIN
images/telnet.png
Normal file
After Width: | Height: | Size: 720 KiB |
BIN
images/web.png
Normal file
After Width: | Height: | Size: 644 KiB |
@ -1,21 +0,0 @@
|
|||||||
[GIN] 2019/08/04 - 20:21:16 | 200 | 1.61658ms | 127.0.0.1 | GET /fish
|
|
||||||
[GIN] 2019/08/04 - 20:21:16 | 200 | 2.738172ms | 127.0.0.1 | GET /get/fish/list
|
|
||||||
[GIN] 2019/08/04 - 20:21:16 | 200 | 4.061843ms | 127.0.0.1 | GET /static/favicon.ico
|
|
||||||
[GIN] 2019/08/04 - 20:22:24 | 200 | 1.333823ms | 127.0.0.1 | GET /fish
|
|
||||||
[GIN] 2019/08/04 - 20:22:24 | 200 | 889.568µs | 127.0.0.1 | GET /get/fish/list
|
|
||||||
[GIN] 2019/08/04 - 20:22:24 | 200 | 770.409µs | 127.0.0.1 | GET /static/favicon.ico
|
|
||||||
[GIN] 2019/08/04 - 20:22:25 | 200 | 902.677µs | 127.0.0.1 | GET /get/fish/info?id=77
|
|
||||||
[GIN] 2019/08/04 - 20:33:01 | 200 | 3.505252ms | 127.0.0.1 | POST /post/fish/del
|
|
||||||
[GIN] 2019/08/04 - 20:33:01 | 200 | 1.243913ms | 127.0.0.1 | GET /fish
|
|
||||||
[GIN] 2019/08/04 - 20:33:01 | 200 | 794.164µs | 127.0.0.1 | GET /get/fish/list
|
|
||||||
[GIN] 2019/08/04 - 20:33:02 | 200 | 3.586026ms | 127.0.0.1 | POST /post/fish/del
|
|
||||||
[GIN] 2019/08/04 - 20:33:02 | 200 | 1.35138ms | 127.0.0.1 | GET /fish
|
|
||||||
[GIN] 2019/08/04 - 20:33:02 | 200 | 650.803µs | 127.0.0.1 | GET /get/fish/list
|
|
||||||
[GIN] 2019/08/04 - 20:33:41 | 200 | 3.068789ms | 127.0.0.1 | POST /api/v1/post/report
|
|
||||||
[GIN] 2019/08/04 - 20:33:45 | 200 | 1.526386ms | 127.0.0.1 | GET /fish
|
|
||||||
[GIN] 2019/08/04 - 20:33:45 | 200 | 781.029µs | 127.0.0.1 | GET /get/fish/list
|
|
||||||
[GIN] 2019/08/04 - 20:33:45 | 200 | 425.853µs | 127.0.0.1 | GET /static/favicon.ico
|
|
||||||
[GIN] 2019/08/04 - 20:33:48 | 200 | 3.641487ms | 127.0.0.1 | POST /post/fish/del
|
|
||||||
[GIN] 2019/08/04 - 20:33:48 | 200 | 1.129608ms | 127.0.0.1 | GET /fish
|
|
||||||
[GIN] 2019/08/04 - 20:33:48 | 200 | 717.537µs | 127.0.0.1 | GET /get/fish/list
|
|
||||||
[GIN] 2019/08/04 - 20:33:49 | 200 | 740.755µs | 127.0.0.1 | GET /get/fish/info?id=78
|
|
4
main.go
@ -1,8 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"HFish/utils/setting"
|
"HFish/utils/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ func main() {
|
|||||||
} else if args[1] == "init" || args[1] == "--init" {
|
} else if args[1] == "init" || args[1] == "--init" {
|
||||||
setting.Init()
|
setting.Init()
|
||||||
} else if args[1] == "version" || args[1] == "--version" {
|
} else if args[1] == "version" || args[1] == "--version" {
|
||||||
fmt.Println("v0.1")
|
fmt.Println("v0.2")
|
||||||
} else if args[1] == "run" || args[1] == "--run" {
|
} else if args[1] == "run" || args[1] == "--run" {
|
||||||
setting.Run()
|
setting.Run()
|
||||||
} else {
|
} else {
|
||||||
|
@ -6619,13 +6619,38 @@ File: Menu
|
|||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatarx{
|
||||||
|
box-shadow: 0 1px 16px rgba(0, 0, 0, 0.53);
|
||||||
|
border: 1px solid #333;
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
/* border: 2px solid #444; */
|
||||||
|
padding: 1px;
|
||||||
|
border-radius: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-border-radius: 100%;
|
||||||
|
-moz-border-radius: 100%;
|
||||||
|
/* box-shadow: inset 0 -1px 0 #3333; */
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
-webkit-transition: -webkit-transform 0.6s ease-out;
|
||||||
|
transition: transform 0.6s ease-out;
|
||||||
|
-moz-transition: -moz-transform 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarx:hover {
|
||||||
|
transform: rotateZ(360deg);
|
||||||
|
-webkit-transform: rotateZ(360deg);
|
||||||
|
-moz-transform: rotateZ(360deg);
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
float: left;
|
float: left;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
font-size: 20px;
|
font-size: 26px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
line-height: 64px;
|
font-family: fantasy;
|
||||||
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo span span {
|
.logo span span {
|
||||||
|
8
static/libs/counterup/jquery.counterup.min.js
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*!
|
||||||
|
* jquery.counterup.js 1.0
|
||||||
|
*
|
||||||
|
* Copyright 2013, Benjamin Intal http://gambit.ph @bfintal
|
||||||
|
* Released under the GPL v2 License
|
||||||
|
*
|
||||||
|
* Date: Nov 26, 2013
|
||||||
|
*/(function(e){"use strict";e.fn.counterUp=function(t){var n=e.extend({time:400,delay:10},t);return this.each(function(){var t=e(this),r=n,i=function(){var e=[],n=r.time/r.delay,i=t.text(),s=/[0-9]+,[0-9]+/.test(i);i=i.replace(/,/g,"");var o=/^[0-9]+$/.test(i),u=/^[0-9]+\.[0-9]+$/.test(i),a=u?(i.split(".")[1]||[]).length:0;for(var f=n;f>=1;f--){var l=parseInt(i/n*f);u&&(l=parseFloat(i/n*f).toFixed(a));if(s)while(/(\d+)(\d{3})/.test(l.toString()))l=l.toString().replace(/(\d+)(\d{3})/,"$1,$2");e.unshift(l)}t.data("counterup-nums",e);t.text("0");var c=function(){t.text(t.data("counterup-nums").shift());if(t.data("counterup-nums").length)setTimeout(t.data("counterup-func"),r.delay);else{delete t.data("counterup-nums");t.data("counterup-nums",null);t.data("counterup-func",null)}};t.data("counterup-func",c);setTimeout(t.data("counterup-func"),r.delay)};t.waypoint(i,{offset:"100%",triggerOnce:!0})})}})(jQuery);
|
1
static/libs/moment/moment.min.js
vendored
Normal file
BIN
static/libs/wangeditor3/fonts/w-e-icon.woff
Normal file
411
static/libs/wangeditor3/wangEditor.css
Normal file
4674
static/libs/wangeditor3/wangEditor.js
Normal file
1
static/libs/wangeditor3/wangEditor.min.css
vendored
Normal file
4
static/libs/wangeditor3/wangEditor.min.js
vendored
Normal file
1
static/libs/wangeditor3/wangEditor.min.js.map
Normal file
647
static/libs/waypoints/lib/jquery.waypoints.js
Normal file
@ -0,0 +1,647 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var keyCounter = 0
|
||||||
|
var allWaypoints = {}
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/waypoint */
|
||||||
|
function Waypoint(options) {
|
||||||
|
if (!options) {
|
||||||
|
throw new Error('No options passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
if (!options.element) {
|
||||||
|
throw new Error('No element option passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
if (!options.handler) {
|
||||||
|
throw new Error('No handler option passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key = 'waypoint-' + keyCounter
|
||||||
|
this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options)
|
||||||
|
this.element = this.options.element
|
||||||
|
this.adapter = new Waypoint.Adapter(this.element)
|
||||||
|
this.callback = options.handler
|
||||||
|
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
|
||||||
|
this.enabled = this.options.enabled
|
||||||
|
this.triggerPoint = null
|
||||||
|
this.group = Waypoint.Group.findOrCreate({
|
||||||
|
name: this.options.group,
|
||||||
|
axis: this.axis
|
||||||
|
})
|
||||||
|
this.context = Waypoint.Context.findOrCreateByElement(this.options.context)
|
||||||
|
|
||||||
|
if (Waypoint.offsetAliases[this.options.offset]) {
|
||||||
|
this.options.offset = Waypoint.offsetAliases[this.options.offset]
|
||||||
|
}
|
||||||
|
this.group.add(this)
|
||||||
|
this.context.add(this)
|
||||||
|
allWaypoints[this.key] = this
|
||||||
|
keyCounter += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.prototype.queueTrigger = function(direction) {
|
||||||
|
this.group.queueTrigger(this, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.prototype.trigger = function(args) {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.callback) {
|
||||||
|
this.callback.apply(this, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/destroy */
|
||||||
|
Waypoint.prototype.destroy = function() {
|
||||||
|
this.context.remove(this)
|
||||||
|
this.group.remove(this)
|
||||||
|
delete allWaypoints[this.key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/disable */
|
||||||
|
Waypoint.prototype.disable = function() {
|
||||||
|
this.enabled = false
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/enable */
|
||||||
|
Waypoint.prototype.enable = function() {
|
||||||
|
this.context.refresh()
|
||||||
|
this.enabled = true
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/next */
|
||||||
|
Waypoint.prototype.next = function() {
|
||||||
|
return this.group.next(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/previous */
|
||||||
|
Waypoint.prototype.previous = function() {
|
||||||
|
return this.group.previous(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.invokeAll = function(method) {
|
||||||
|
var allWaypointsArray = []
|
||||||
|
for (var waypointKey in allWaypoints) {
|
||||||
|
allWaypointsArray.push(allWaypoints[waypointKey])
|
||||||
|
}
|
||||||
|
for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
|
||||||
|
allWaypointsArray[i][method]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/destroy-all */
|
||||||
|
Waypoint.destroyAll = function() {
|
||||||
|
Waypoint.invokeAll('destroy')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/disable-all */
|
||||||
|
Waypoint.disableAll = function() {
|
||||||
|
Waypoint.invokeAll('disable')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/enable-all */
|
||||||
|
Waypoint.enableAll = function() {
|
||||||
|
Waypoint.invokeAll('enable')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/refresh-all */
|
||||||
|
Waypoint.refreshAll = function() {
|
||||||
|
Waypoint.Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/viewport-height */
|
||||||
|
Waypoint.viewportHeight = function() {
|
||||||
|
return window.innerHeight || document.documentElement.clientHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/viewport-width */
|
||||||
|
Waypoint.viewportWidth = function() {
|
||||||
|
return document.documentElement.clientWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.adapters = []
|
||||||
|
|
||||||
|
Waypoint.defaults = {
|
||||||
|
context: window,
|
||||||
|
continuous: true,
|
||||||
|
enabled: true,
|
||||||
|
group: 'default',
|
||||||
|
horizontal: false,
|
||||||
|
offset: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.offsetAliases = {
|
||||||
|
'bottom-in-view': function() {
|
||||||
|
return this.context.innerHeight() - this.adapter.outerHeight()
|
||||||
|
},
|
||||||
|
'right-in-view': function() {
|
||||||
|
return this.context.innerWidth() - this.adapter.outerWidth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Waypoint = Waypoint
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function requestAnimationFrameShim(callback) {
|
||||||
|
window.setTimeout(callback, 1000 / 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyCounter = 0
|
||||||
|
var contexts = {}
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
var oldWindowLoad = window.onload
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context */
|
||||||
|
function Context(element) {
|
||||||
|
this.element = element
|
||||||
|
this.Adapter = Waypoint.Adapter
|
||||||
|
this.adapter = new this.Adapter(element)
|
||||||
|
this.key = 'waypoint-context-' + keyCounter
|
||||||
|
this.didScroll = false
|
||||||
|
this.didResize = false
|
||||||
|
this.oldScroll = {
|
||||||
|
x: this.adapter.scrollLeft(),
|
||||||
|
y: this.adapter.scrollTop()
|
||||||
|
}
|
||||||
|
this.waypoints = {
|
||||||
|
vertical: {},
|
||||||
|
horizontal: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.waypointContextKey = this.key
|
||||||
|
contexts[element.waypointContextKey] = this
|
||||||
|
keyCounter += 1
|
||||||
|
|
||||||
|
this.createThrottledScrollHandler()
|
||||||
|
this.createThrottledResizeHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.add = function(waypoint) {
|
||||||
|
var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
|
||||||
|
this.waypoints[axis][waypoint.key] = waypoint
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.checkEmpty = function() {
|
||||||
|
var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal)
|
||||||
|
var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical)
|
||||||
|
if (horizontalEmpty && verticalEmpty) {
|
||||||
|
this.adapter.off('.waypoints')
|
||||||
|
delete contexts[this.key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.createThrottledResizeHandler = function() {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
function resizeHandler() {
|
||||||
|
self.handleResize()
|
||||||
|
self.didResize = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adapter.on('resize.waypoints', function() {
|
||||||
|
if (!self.didResize) {
|
||||||
|
self.didResize = true
|
||||||
|
Waypoint.requestAnimationFrame(resizeHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.createThrottledScrollHandler = function() {
|
||||||
|
var self = this
|
||||||
|
function scrollHandler() {
|
||||||
|
self.handleScroll()
|
||||||
|
self.didScroll = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adapter.on('scroll.waypoints', function() {
|
||||||
|
if (!self.didScroll || Waypoint.isTouch) {
|
||||||
|
self.didScroll = true
|
||||||
|
Waypoint.requestAnimationFrame(scrollHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.handleResize = function() {
|
||||||
|
Waypoint.Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.handleScroll = function() {
|
||||||
|
var triggeredGroups = {}
|
||||||
|
var axes = {
|
||||||
|
horizontal: {
|
||||||
|
newScroll: this.adapter.scrollLeft(),
|
||||||
|
oldScroll: this.oldScroll.x,
|
||||||
|
forward: 'right',
|
||||||
|
backward: 'left'
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
newScroll: this.adapter.scrollTop(),
|
||||||
|
oldScroll: this.oldScroll.y,
|
||||||
|
forward: 'down',
|
||||||
|
backward: 'up'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var axisKey in axes) {
|
||||||
|
var axis = axes[axisKey]
|
||||||
|
var isForward = axis.newScroll > axis.oldScroll
|
||||||
|
var direction = isForward ? axis.forward : axis.backward
|
||||||
|
|
||||||
|
for (var waypointKey in this.waypoints[axisKey]) {
|
||||||
|
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||||
|
var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
|
||||||
|
var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
|
||||||
|
var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
|
||||||
|
var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint
|
||||||
|
if (crossedForward || crossedBackward) {
|
||||||
|
waypoint.queueTrigger(direction)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var groupKey in triggeredGroups) {
|
||||||
|
triggeredGroups[groupKey].flushTriggers()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oldScroll = {
|
||||||
|
x: axes.horizontal.newScroll,
|
||||||
|
y: axes.vertical.newScroll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.innerHeight = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
if (this.element == this.element.window) {
|
||||||
|
return Waypoint.viewportHeight()
|
||||||
|
}
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
return this.adapter.innerHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.remove = function(waypoint) {
|
||||||
|
delete this.waypoints[waypoint.axis][waypoint.key]
|
||||||
|
this.checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.innerWidth = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
if (this.element == this.element.window) {
|
||||||
|
return Waypoint.viewportWidth()
|
||||||
|
}
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
return this.adapter.innerWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-destroy */
|
||||||
|
Context.prototype.destroy = function() {
|
||||||
|
var allWaypoints = []
|
||||||
|
for (var axis in this.waypoints) {
|
||||||
|
for (var waypointKey in this.waypoints[axis]) {
|
||||||
|
allWaypoints.push(this.waypoints[axis][waypointKey])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0, end = allWaypoints.length; i < end; i++) {
|
||||||
|
allWaypoints[i].destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-refresh */
|
||||||
|
Context.prototype.refresh = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
var isWindow = this.element == this.element.window
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
var contextOffset = this.adapter.offset()
|
||||||
|
var triggeredGroups = {}
|
||||||
|
var axes
|
||||||
|
|
||||||
|
this.handleScroll()
|
||||||
|
axes = {
|
||||||
|
horizontal: {
|
||||||
|
contextOffset: isWindow ? 0 : contextOffset.left,
|
||||||
|
contextScroll: isWindow ? 0 : this.oldScroll.x,
|
||||||
|
contextDimension: this.innerWidth(),
|
||||||
|
oldScroll: this.oldScroll.x,
|
||||||
|
forward: 'right',
|
||||||
|
backward: 'left',
|
||||||
|
offsetProp: 'left'
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
contextOffset: isWindow ? 0 : contextOffset.top,
|
||||||
|
contextScroll: isWindow ? 0 : this.oldScroll.y,
|
||||||
|
contextDimension: this.innerHeight(),
|
||||||
|
oldScroll: this.oldScroll.y,
|
||||||
|
forward: 'down',
|
||||||
|
backward: 'up',
|
||||||
|
offsetProp: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var axisKey in axes) {
|
||||||
|
var axis = axes[axisKey]
|
||||||
|
for (var waypointKey in this.waypoints[axisKey]) {
|
||||||
|
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||||
|
var adjustment = waypoint.options.offset
|
||||||
|
var oldTriggerPoint = waypoint.triggerPoint
|
||||||
|
var elementOffset = 0
|
||||||
|
var freshWaypoint = oldTriggerPoint == null
|
||||||
|
var contextModifier, wasBeforeScroll, nowAfterScroll
|
||||||
|
var triggeredBackward, triggeredForward
|
||||||
|
|
||||||
|
if (waypoint.element !== waypoint.element.window) {
|
||||||
|
elementOffset = waypoint.adapter.offset()[axis.offsetProp]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof adjustment === 'function') {
|
||||||
|
adjustment = adjustment.apply(waypoint)
|
||||||
|
}
|
||||||
|
else if (typeof adjustment === 'string') {
|
||||||
|
adjustment = parseFloat(adjustment)
|
||||||
|
if (waypoint.options.offset.indexOf('%') > - 1) {
|
||||||
|
adjustment = Math.ceil(axis.contextDimension * adjustment / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contextModifier = axis.contextScroll - axis.contextOffset
|
||||||
|
waypoint.triggerPoint = elementOffset + contextModifier - adjustment
|
||||||
|
wasBeforeScroll = oldTriggerPoint < axis.oldScroll
|
||||||
|
nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
|
||||||
|
triggeredBackward = wasBeforeScroll && nowAfterScroll
|
||||||
|
triggeredForward = !wasBeforeScroll && !nowAfterScroll
|
||||||
|
|
||||||
|
if (!freshWaypoint && triggeredBackward) {
|
||||||
|
waypoint.queueTrigger(axis.backward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
else if (!freshWaypoint && triggeredForward) {
|
||||||
|
waypoint.queueTrigger(axis.forward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
|
||||||
|
waypoint.queueTrigger(axis.forward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var groupKey in triggeredGroups) {
|
||||||
|
triggeredGroups[groupKey].flushTriggers()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.findOrCreateByElement = function(element) {
|
||||||
|
return Context.findByElement(element) || new Context(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.refreshAll = function() {
|
||||||
|
for (var contextId in contexts) {
|
||||||
|
contexts[contextId].refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-find-by-element */
|
||||||
|
Context.findByElement = function(element) {
|
||||||
|
return contexts[element.waypointContextKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
if (oldWindowLoad) {
|
||||||
|
oldWindowLoad()
|
||||||
|
}
|
||||||
|
Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.requestAnimationFrame = function(callback) {
|
||||||
|
var requestFn = window.requestAnimationFrame ||
|
||||||
|
window.mozRequestAnimationFrame ||
|
||||||
|
window.webkitRequestAnimationFrame ||
|
||||||
|
requestAnimationFrameShim
|
||||||
|
requestFn.call(window, callback)
|
||||||
|
}
|
||||||
|
Waypoint.Context = Context
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function byTriggerPoint(a, b) {
|
||||||
|
return a.triggerPoint - b.triggerPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
function byReverseTriggerPoint(a, b) {
|
||||||
|
return b.triggerPoint - a.triggerPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
var groups = {
|
||||||
|
vertical: {},
|
||||||
|
horizontal: {}
|
||||||
|
}
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/group */
|
||||||
|
function Group(options) {
|
||||||
|
this.name = options.name
|
||||||
|
this.axis = options.axis
|
||||||
|
this.id = this.name + '-' + this.axis
|
||||||
|
this.waypoints = []
|
||||||
|
this.clearTriggerQueues()
|
||||||
|
groups[this.axis][this.name] = this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.add = function(waypoint) {
|
||||||
|
this.waypoints.push(waypoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.clearTriggerQueues = function() {
|
||||||
|
this.triggerQueues = {
|
||||||
|
up: [],
|
||||||
|
down: [],
|
||||||
|
left: [],
|
||||||
|
right: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.flushTriggers = function() {
|
||||||
|
for (var direction in this.triggerQueues) {
|
||||||
|
var waypoints = this.triggerQueues[direction]
|
||||||
|
var reverse = direction === 'up' || direction === 'left'
|
||||||
|
waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint)
|
||||||
|
for (var i = 0, end = waypoints.length; i < end; i += 1) {
|
||||||
|
var waypoint = waypoints[i]
|
||||||
|
if (waypoint.options.continuous || i === waypoints.length - 1) {
|
||||||
|
waypoint.trigger([direction])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.clearTriggerQueues()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.next = function(waypoint) {
|
||||||
|
this.waypoints.sort(byTriggerPoint)
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
var isLast = index === this.waypoints.length - 1
|
||||||
|
return isLast ? null : this.waypoints[index + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.previous = function(waypoint) {
|
||||||
|
this.waypoints.sort(byTriggerPoint)
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
return index ? this.waypoints[index - 1] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.queueTrigger = function(waypoint, direction) {
|
||||||
|
this.triggerQueues[direction].push(waypoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.remove = function(waypoint) {
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
if (index > -1) {
|
||||||
|
this.waypoints.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/first */
|
||||||
|
Group.prototype.first = function() {
|
||||||
|
return this.waypoints[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/last */
|
||||||
|
Group.prototype.last = function() {
|
||||||
|
return this.waypoints[this.waypoints.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.findOrCreate = function(options) {
|
||||||
|
return groups[options.axis][options.name] || new Group(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.Group = Group
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var $ = window.jQuery
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
function JQueryAdapter(element) {
|
||||||
|
this.$element = $(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each([
|
||||||
|
'innerHeight',
|
||||||
|
'innerWidth',
|
||||||
|
'off',
|
||||||
|
'offset',
|
||||||
|
'on',
|
||||||
|
'outerHeight',
|
||||||
|
'outerWidth',
|
||||||
|
'scrollLeft',
|
||||||
|
'scrollTop'
|
||||||
|
], function(i, method) {
|
||||||
|
JQueryAdapter.prototype[method] = function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments)
|
||||||
|
return this.$element[method].apply(this.$element, args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
$.each([
|
||||||
|
'extend',
|
||||||
|
'inArray',
|
||||||
|
'isEmptyObject'
|
||||||
|
], function(i, method) {
|
||||||
|
JQueryAdapter[method] = $[method]
|
||||||
|
})
|
||||||
|
|
||||||
|
Waypoint.adapters.push({
|
||||||
|
name: 'jquery',
|
||||||
|
Adapter: JQueryAdapter
|
||||||
|
})
|
||||||
|
Waypoint.Adapter = JQueryAdapter
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
function createExtension(framework) {
|
||||||
|
return function() {
|
||||||
|
var waypoints = []
|
||||||
|
var overrides = arguments[0]
|
||||||
|
|
||||||
|
if (framework.isFunction(arguments[0])) {
|
||||||
|
overrides = framework.extend({}, arguments[1])
|
||||||
|
overrides.handler = arguments[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.each(function() {
|
||||||
|
var options = framework.extend({}, overrides, {
|
||||||
|
element: this
|
||||||
|
})
|
||||||
|
if (typeof options.context === 'string') {
|
||||||
|
options.context = framework(this).closest(options.context)[0]
|
||||||
|
}
|
||||||
|
waypoints.push(new Waypoint(options))
|
||||||
|
})
|
||||||
|
|
||||||
|
return waypoints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.jQuery) {
|
||||||
|
window.jQuery.fn.waypoint = createExtension(window.jQuery)
|
||||||
|
}
|
||||||
|
if (window.Zepto) {
|
||||||
|
window.Zepto.fn.waypoint = createExtension(window.Zepto)
|
||||||
|
}
|
||||||
|
}())
|
||||||
|
;
|
7
static/libs/waypoints/lib/jquery.waypoints.min.js
vendored
Normal file
743
static/libs/waypoints/lib/noframework.waypoints.js
Normal file
@ -0,0 +1,743 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var keyCounter = 0
|
||||||
|
var allWaypoints = {}
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/waypoint */
|
||||||
|
function Waypoint(options) {
|
||||||
|
if (!options) {
|
||||||
|
throw new Error('No options passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
if (!options.element) {
|
||||||
|
throw new Error('No element option passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
if (!options.handler) {
|
||||||
|
throw new Error('No handler option passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key = 'waypoint-' + keyCounter
|
||||||
|
this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options)
|
||||||
|
this.element = this.options.element
|
||||||
|
this.adapter = new Waypoint.Adapter(this.element)
|
||||||
|
this.callback = options.handler
|
||||||
|
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
|
||||||
|
this.enabled = this.options.enabled
|
||||||
|
this.triggerPoint = null
|
||||||
|
this.group = Waypoint.Group.findOrCreate({
|
||||||
|
name: this.options.group,
|
||||||
|
axis: this.axis
|
||||||
|
})
|
||||||
|
this.context = Waypoint.Context.findOrCreateByElement(this.options.context)
|
||||||
|
|
||||||
|
if (Waypoint.offsetAliases[this.options.offset]) {
|
||||||
|
this.options.offset = Waypoint.offsetAliases[this.options.offset]
|
||||||
|
}
|
||||||
|
this.group.add(this)
|
||||||
|
this.context.add(this)
|
||||||
|
allWaypoints[this.key] = this
|
||||||
|
keyCounter += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.prototype.queueTrigger = function(direction) {
|
||||||
|
this.group.queueTrigger(this, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.prototype.trigger = function(args) {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.callback) {
|
||||||
|
this.callback.apply(this, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/destroy */
|
||||||
|
Waypoint.prototype.destroy = function() {
|
||||||
|
this.context.remove(this)
|
||||||
|
this.group.remove(this)
|
||||||
|
delete allWaypoints[this.key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/disable */
|
||||||
|
Waypoint.prototype.disable = function() {
|
||||||
|
this.enabled = false
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/enable */
|
||||||
|
Waypoint.prototype.enable = function() {
|
||||||
|
this.context.refresh()
|
||||||
|
this.enabled = true
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/next */
|
||||||
|
Waypoint.prototype.next = function() {
|
||||||
|
return this.group.next(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/previous */
|
||||||
|
Waypoint.prototype.previous = function() {
|
||||||
|
return this.group.previous(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.invokeAll = function(method) {
|
||||||
|
var allWaypointsArray = []
|
||||||
|
for (var waypointKey in allWaypoints) {
|
||||||
|
allWaypointsArray.push(allWaypoints[waypointKey])
|
||||||
|
}
|
||||||
|
for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
|
||||||
|
allWaypointsArray[i][method]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/destroy-all */
|
||||||
|
Waypoint.destroyAll = function() {
|
||||||
|
Waypoint.invokeAll('destroy')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/disable-all */
|
||||||
|
Waypoint.disableAll = function() {
|
||||||
|
Waypoint.invokeAll('disable')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/enable-all */
|
||||||
|
Waypoint.enableAll = function() {
|
||||||
|
Waypoint.invokeAll('enable')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/refresh-all */
|
||||||
|
Waypoint.refreshAll = function() {
|
||||||
|
Waypoint.Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/viewport-height */
|
||||||
|
Waypoint.viewportHeight = function() {
|
||||||
|
return window.innerHeight || document.documentElement.clientHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/viewport-width */
|
||||||
|
Waypoint.viewportWidth = function() {
|
||||||
|
return document.documentElement.clientWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.adapters = []
|
||||||
|
|
||||||
|
Waypoint.defaults = {
|
||||||
|
context: window,
|
||||||
|
continuous: true,
|
||||||
|
enabled: true,
|
||||||
|
group: 'default',
|
||||||
|
horizontal: false,
|
||||||
|
offset: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.offsetAliases = {
|
||||||
|
'bottom-in-view': function() {
|
||||||
|
return this.context.innerHeight() - this.adapter.outerHeight()
|
||||||
|
},
|
||||||
|
'right-in-view': function() {
|
||||||
|
return this.context.innerWidth() - this.adapter.outerWidth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Waypoint = Waypoint
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function requestAnimationFrameShim(callback) {
|
||||||
|
window.setTimeout(callback, 1000 / 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyCounter = 0
|
||||||
|
var contexts = {}
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
var oldWindowLoad = window.onload
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context */
|
||||||
|
function Context(element) {
|
||||||
|
this.element = element
|
||||||
|
this.Adapter = Waypoint.Adapter
|
||||||
|
this.adapter = new this.Adapter(element)
|
||||||
|
this.key = 'waypoint-context-' + keyCounter
|
||||||
|
this.didScroll = false
|
||||||
|
this.didResize = false
|
||||||
|
this.oldScroll = {
|
||||||
|
x: this.adapter.scrollLeft(),
|
||||||
|
y: this.adapter.scrollTop()
|
||||||
|
}
|
||||||
|
this.waypoints = {
|
||||||
|
vertical: {},
|
||||||
|
horizontal: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.waypointContextKey = this.key
|
||||||
|
contexts[element.waypointContextKey] = this
|
||||||
|
keyCounter += 1
|
||||||
|
|
||||||
|
this.createThrottledScrollHandler()
|
||||||
|
this.createThrottledResizeHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.add = function(waypoint) {
|
||||||
|
var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
|
||||||
|
this.waypoints[axis][waypoint.key] = waypoint
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.checkEmpty = function() {
|
||||||
|
var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal)
|
||||||
|
var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical)
|
||||||
|
if (horizontalEmpty && verticalEmpty) {
|
||||||
|
this.adapter.off('.waypoints')
|
||||||
|
delete contexts[this.key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.createThrottledResizeHandler = function() {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
function resizeHandler() {
|
||||||
|
self.handleResize()
|
||||||
|
self.didResize = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adapter.on('resize.waypoints', function() {
|
||||||
|
if (!self.didResize) {
|
||||||
|
self.didResize = true
|
||||||
|
Waypoint.requestAnimationFrame(resizeHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.createThrottledScrollHandler = function() {
|
||||||
|
var self = this
|
||||||
|
function scrollHandler() {
|
||||||
|
self.handleScroll()
|
||||||
|
self.didScroll = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adapter.on('scroll.waypoints', function() {
|
||||||
|
if (!self.didScroll || Waypoint.isTouch) {
|
||||||
|
self.didScroll = true
|
||||||
|
Waypoint.requestAnimationFrame(scrollHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.handleResize = function() {
|
||||||
|
Waypoint.Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.handleScroll = function() {
|
||||||
|
var triggeredGroups = {}
|
||||||
|
var axes = {
|
||||||
|
horizontal: {
|
||||||
|
newScroll: this.adapter.scrollLeft(),
|
||||||
|
oldScroll: this.oldScroll.x,
|
||||||
|
forward: 'right',
|
||||||
|
backward: 'left'
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
newScroll: this.adapter.scrollTop(),
|
||||||
|
oldScroll: this.oldScroll.y,
|
||||||
|
forward: 'down',
|
||||||
|
backward: 'up'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var axisKey in axes) {
|
||||||
|
var axis = axes[axisKey]
|
||||||
|
var isForward = axis.newScroll > axis.oldScroll
|
||||||
|
var direction = isForward ? axis.forward : axis.backward
|
||||||
|
|
||||||
|
for (var waypointKey in this.waypoints[axisKey]) {
|
||||||
|
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||||
|
var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
|
||||||
|
var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
|
||||||
|
var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
|
||||||
|
var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint
|
||||||
|
if (crossedForward || crossedBackward) {
|
||||||
|
waypoint.queueTrigger(direction)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var groupKey in triggeredGroups) {
|
||||||
|
triggeredGroups[groupKey].flushTriggers()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oldScroll = {
|
||||||
|
x: axes.horizontal.newScroll,
|
||||||
|
y: axes.vertical.newScroll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.innerHeight = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
if (this.element == this.element.window) {
|
||||||
|
return Waypoint.viewportHeight()
|
||||||
|
}
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
return this.adapter.innerHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.remove = function(waypoint) {
|
||||||
|
delete this.waypoints[waypoint.axis][waypoint.key]
|
||||||
|
this.checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.innerWidth = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
if (this.element == this.element.window) {
|
||||||
|
return Waypoint.viewportWidth()
|
||||||
|
}
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
return this.adapter.innerWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-destroy */
|
||||||
|
Context.prototype.destroy = function() {
|
||||||
|
var allWaypoints = []
|
||||||
|
for (var axis in this.waypoints) {
|
||||||
|
for (var waypointKey in this.waypoints[axis]) {
|
||||||
|
allWaypoints.push(this.waypoints[axis][waypointKey])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0, end = allWaypoints.length; i < end; i++) {
|
||||||
|
allWaypoints[i].destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-refresh */
|
||||||
|
Context.prototype.refresh = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
var isWindow = this.element == this.element.window
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
var contextOffset = this.adapter.offset()
|
||||||
|
var triggeredGroups = {}
|
||||||
|
var axes
|
||||||
|
|
||||||
|
this.handleScroll()
|
||||||
|
axes = {
|
||||||
|
horizontal: {
|
||||||
|
contextOffset: isWindow ? 0 : contextOffset.left,
|
||||||
|
contextScroll: isWindow ? 0 : this.oldScroll.x,
|
||||||
|
contextDimension: this.innerWidth(),
|
||||||
|
oldScroll: this.oldScroll.x,
|
||||||
|
forward: 'right',
|
||||||
|
backward: 'left',
|
||||||
|
offsetProp: 'left'
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
contextOffset: isWindow ? 0 : contextOffset.top,
|
||||||
|
contextScroll: isWindow ? 0 : this.oldScroll.y,
|
||||||
|
contextDimension: this.innerHeight(),
|
||||||
|
oldScroll: this.oldScroll.y,
|
||||||
|
forward: 'down',
|
||||||
|
backward: 'up',
|
||||||
|
offsetProp: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var axisKey in axes) {
|
||||||
|
var axis = axes[axisKey]
|
||||||
|
for (var waypointKey in this.waypoints[axisKey]) {
|
||||||
|
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||||
|
var adjustment = waypoint.options.offset
|
||||||
|
var oldTriggerPoint = waypoint.triggerPoint
|
||||||
|
var elementOffset = 0
|
||||||
|
var freshWaypoint = oldTriggerPoint == null
|
||||||
|
var contextModifier, wasBeforeScroll, nowAfterScroll
|
||||||
|
var triggeredBackward, triggeredForward
|
||||||
|
|
||||||
|
if (waypoint.element !== waypoint.element.window) {
|
||||||
|
elementOffset = waypoint.adapter.offset()[axis.offsetProp]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof adjustment === 'function') {
|
||||||
|
adjustment = adjustment.apply(waypoint)
|
||||||
|
}
|
||||||
|
else if (typeof adjustment === 'string') {
|
||||||
|
adjustment = parseFloat(adjustment)
|
||||||
|
if (waypoint.options.offset.indexOf('%') > - 1) {
|
||||||
|
adjustment = Math.ceil(axis.contextDimension * adjustment / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contextModifier = axis.contextScroll - axis.contextOffset
|
||||||
|
waypoint.triggerPoint = elementOffset + contextModifier - adjustment
|
||||||
|
wasBeforeScroll = oldTriggerPoint < axis.oldScroll
|
||||||
|
nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
|
||||||
|
triggeredBackward = wasBeforeScroll && nowAfterScroll
|
||||||
|
triggeredForward = !wasBeforeScroll && !nowAfterScroll
|
||||||
|
|
||||||
|
if (!freshWaypoint && triggeredBackward) {
|
||||||
|
waypoint.queueTrigger(axis.backward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
else if (!freshWaypoint && triggeredForward) {
|
||||||
|
waypoint.queueTrigger(axis.forward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
|
||||||
|
waypoint.queueTrigger(axis.forward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var groupKey in triggeredGroups) {
|
||||||
|
triggeredGroups[groupKey].flushTriggers()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.findOrCreateByElement = function(element) {
|
||||||
|
return Context.findByElement(element) || new Context(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.refreshAll = function() {
|
||||||
|
for (var contextId in contexts) {
|
||||||
|
contexts[contextId].refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-find-by-element */
|
||||||
|
Context.findByElement = function(element) {
|
||||||
|
return contexts[element.waypointContextKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
if (oldWindowLoad) {
|
||||||
|
oldWindowLoad()
|
||||||
|
}
|
||||||
|
Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.requestAnimationFrame = function(callback) {
|
||||||
|
var requestFn = window.requestAnimationFrame ||
|
||||||
|
window.mozRequestAnimationFrame ||
|
||||||
|
window.webkitRequestAnimationFrame ||
|
||||||
|
requestAnimationFrameShim
|
||||||
|
requestFn.call(window, callback)
|
||||||
|
}
|
||||||
|
Waypoint.Context = Context
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function byTriggerPoint(a, b) {
|
||||||
|
return a.triggerPoint - b.triggerPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
function byReverseTriggerPoint(a, b) {
|
||||||
|
return b.triggerPoint - a.triggerPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
var groups = {
|
||||||
|
vertical: {},
|
||||||
|
horizontal: {}
|
||||||
|
}
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/group */
|
||||||
|
function Group(options) {
|
||||||
|
this.name = options.name
|
||||||
|
this.axis = options.axis
|
||||||
|
this.id = this.name + '-' + this.axis
|
||||||
|
this.waypoints = []
|
||||||
|
this.clearTriggerQueues()
|
||||||
|
groups[this.axis][this.name] = this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.add = function(waypoint) {
|
||||||
|
this.waypoints.push(waypoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.clearTriggerQueues = function() {
|
||||||
|
this.triggerQueues = {
|
||||||
|
up: [],
|
||||||
|
down: [],
|
||||||
|
left: [],
|
||||||
|
right: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.flushTriggers = function() {
|
||||||
|
for (var direction in this.triggerQueues) {
|
||||||
|
var waypoints = this.triggerQueues[direction]
|
||||||
|
var reverse = direction === 'up' || direction === 'left'
|
||||||
|
waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint)
|
||||||
|
for (var i = 0, end = waypoints.length; i < end; i += 1) {
|
||||||
|
var waypoint = waypoints[i]
|
||||||
|
if (waypoint.options.continuous || i === waypoints.length - 1) {
|
||||||
|
waypoint.trigger([direction])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.clearTriggerQueues()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.next = function(waypoint) {
|
||||||
|
this.waypoints.sort(byTriggerPoint)
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
var isLast = index === this.waypoints.length - 1
|
||||||
|
return isLast ? null : this.waypoints[index + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.previous = function(waypoint) {
|
||||||
|
this.waypoints.sort(byTriggerPoint)
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
return index ? this.waypoints[index - 1] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.queueTrigger = function(waypoint, direction) {
|
||||||
|
this.triggerQueues[direction].push(waypoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.remove = function(waypoint) {
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
if (index > -1) {
|
||||||
|
this.waypoints.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/first */
|
||||||
|
Group.prototype.first = function() {
|
||||||
|
return this.waypoints[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/last */
|
||||||
|
Group.prototype.last = function() {
|
||||||
|
return this.waypoints[this.waypoints.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.findOrCreate = function(options) {
|
||||||
|
return groups[options.axis][options.name] || new Group(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.Group = Group
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
function isWindow(element) {
|
||||||
|
return element === element.window
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWindow(element) {
|
||||||
|
if (isWindow(element)) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
return element.defaultView
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoFrameworkAdapter(element) {
|
||||||
|
this.element = element
|
||||||
|
this.handlers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.prototype.innerHeight = function() {
|
||||||
|
var isWin = isWindow(this.element)
|
||||||
|
return isWin ? this.element.innerHeight : this.element.clientHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.prototype.innerWidth = function() {
|
||||||
|
var isWin = isWindow(this.element)
|
||||||
|
return isWin ? this.element.innerWidth : this.element.clientWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.prototype.off = function(event, handler) {
|
||||||
|
function removeListeners(element, listeners, handler) {
|
||||||
|
for (var i = 0, end = listeners.length - 1; i < end; i++) {
|
||||||
|
var listener = listeners[i]
|
||||||
|
if (!handler || handler === listener) {
|
||||||
|
element.removeEventListener(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var eventParts = event.split('.')
|
||||||
|
var eventType = eventParts[0]
|
||||||
|
var namespace = eventParts[1]
|
||||||
|
var element = this.element
|
||||||
|
|
||||||
|
if (namespace && this.handlers[namespace] && eventType) {
|
||||||
|
removeListeners(element, this.handlers[namespace][eventType], handler)
|
||||||
|
this.handlers[namespace][eventType] = []
|
||||||
|
}
|
||||||
|
else if (eventType) {
|
||||||
|
for (var ns in this.handlers) {
|
||||||
|
removeListeners(element, this.handlers[ns][eventType] || [], handler)
|
||||||
|
this.handlers[ns][eventType] = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (namespace && this.handlers[namespace]) {
|
||||||
|
for (var type in this.handlers[namespace]) {
|
||||||
|
removeListeners(element, this.handlers[namespace][type], handler)
|
||||||
|
}
|
||||||
|
this.handlers[namespace] = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adapted from jQuery 1.x offset() */
|
||||||
|
NoFrameworkAdapter.prototype.offset = function() {
|
||||||
|
if (!this.element.ownerDocument) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var documentElement = this.element.ownerDocument.documentElement
|
||||||
|
var win = getWindow(this.element.ownerDocument)
|
||||||
|
var rect = {
|
||||||
|
top: 0,
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.element.getBoundingClientRect) {
|
||||||
|
rect = this.element.getBoundingClientRect()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: rect.top + win.pageYOffset - documentElement.clientTop,
|
||||||
|
left: rect.left + win.pageXOffset - documentElement.clientLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.prototype.on = function(event, handler) {
|
||||||
|
var eventParts = event.split('.')
|
||||||
|
var eventType = eventParts[0]
|
||||||
|
var namespace = eventParts[1] || '__default'
|
||||||
|
var nsHandlers = this.handlers[namespace] = this.handlers[namespace] || {}
|
||||||
|
var nsTypeList = nsHandlers[eventType] = nsHandlers[eventType] || []
|
||||||
|
|
||||||
|
nsTypeList.push(handler)
|
||||||
|
this.element.addEventListener(eventType, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.prototype.outerHeight = function(includeMargin) {
|
||||||
|
var height = this.innerHeight()
|
||||||
|
var computedStyle
|
||||||
|
|
||||||
|
if (includeMargin && !isWindow(this.element)) {
|
||||||
|
computedStyle = window.getComputedStyle(this.element)
|
||||||
|
height += parseInt(computedStyle.marginTop, 10)
|
||||||
|
height += parseInt(computedStyle.marginBottom, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.prototype.outerWidth = function(includeMargin) {
|
||||||
|
var width = this.innerWidth()
|
||||||
|
var computedStyle
|
||||||
|
|
||||||
|
if (includeMargin && !isWindow(this.element)) {
|
||||||
|
computedStyle = window.getComputedStyle(this.element)
|
||||||
|
width += parseInt(computedStyle.marginLeft, 10)
|
||||||
|
width += parseInt(computedStyle.marginRight, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
return width
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.prototype.scrollLeft = function() {
|
||||||
|
var win = getWindow(this.element)
|
||||||
|
return win ? win.pageXOffset : this.element.scrollLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.prototype.scrollTop = function() {
|
||||||
|
var win = getWindow(this.element)
|
||||||
|
return win ? win.pageYOffset : this.element.scrollTop
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.extend = function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments)
|
||||||
|
|
||||||
|
function merge(target, obj) {
|
||||||
|
if (typeof target === 'object' && typeof obj === 'object') {
|
||||||
|
for (var key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
target[key] = obj[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 1, end = args.length; i < end; i++) {
|
||||||
|
merge(args[0], args[i])
|
||||||
|
}
|
||||||
|
return args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.inArray = function(element, array, i) {
|
||||||
|
return array == null ? -1 : array.indexOf(element, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFrameworkAdapter.isEmptyObject = function(obj) {
|
||||||
|
/* eslint no-unused-vars: 0 */
|
||||||
|
for (var name in obj) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.adapters.push({
|
||||||
|
name: 'noframework',
|
||||||
|
Adapter: NoFrameworkAdapter
|
||||||
|
})
|
||||||
|
Waypoint.Adapter = NoFrameworkAdapter
|
||||||
|
}())
|
||||||
|
;
|
7
static/libs/waypoints/lib/noframework.waypoints.min.js
vendored
Normal file
84
static/libs/waypoints/lib/shortcuts/infinite.js
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints Infinite Scroll Shortcut - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var $ = window.jQuery
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/shortcuts/infinite-scroll */
|
||||||
|
function Infinite(options) {
|
||||||
|
this.options = $.extend({}, Infinite.defaults, options)
|
||||||
|
this.container = this.options.element
|
||||||
|
if (this.options.container !== 'auto') {
|
||||||
|
this.container = this.options.container
|
||||||
|
}
|
||||||
|
this.$container = $(this.container)
|
||||||
|
this.$more = $(this.options.more)
|
||||||
|
|
||||||
|
if (this.$more.length) {
|
||||||
|
this.setupHandler()
|
||||||
|
this.waypoint = new Waypoint(this.options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Infinite.prototype.setupHandler = function() {
|
||||||
|
this.options.handler = $.proxy(function() {
|
||||||
|
this.options.onBeforePageLoad()
|
||||||
|
this.destroy()
|
||||||
|
this.$container.addClass(this.options.loadingClass)
|
||||||
|
|
||||||
|
$.get($(this.options.more).attr('href'), $.proxy(function(data) {
|
||||||
|
var $data = $($.parseHTML(data))
|
||||||
|
var $newMore = $data.find(this.options.more)
|
||||||
|
|
||||||
|
var $items = $data.find(this.options.items)
|
||||||
|
if (!$items.length) {
|
||||||
|
$items = $data.filter(this.options.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$container.append($items)
|
||||||
|
this.$container.removeClass(this.options.loadingClass)
|
||||||
|
|
||||||
|
if (!$newMore.length) {
|
||||||
|
$newMore = $data.filter(this.options.more)
|
||||||
|
}
|
||||||
|
if ($newMore.length) {
|
||||||
|
this.$more.replaceWith($newMore)
|
||||||
|
this.$more = $newMore
|
||||||
|
this.waypoint = new Waypoint(this.options)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.$more.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.options.onAfterPageLoad()
|
||||||
|
}, this))
|
||||||
|
}, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
Infinite.prototype.destroy = function() {
|
||||||
|
if (this.waypoint) {
|
||||||
|
this.waypoint.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Infinite.defaults = {
|
||||||
|
container: 'auto',
|
||||||
|
items: '.infinite-item',
|
||||||
|
more: '.infinite-more-link',
|
||||||
|
offset: 'bottom-in-view',
|
||||||
|
loadingClass: 'infinite-loading',
|
||||||
|
onBeforePageLoad: $.noop,
|
||||||
|
onAfterPageLoad: $.noop
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.Infinite = Infinite
|
||||||
|
}())
|
||||||
|
;
|
7
static/libs/waypoints/lib/shortcuts/infinite.min.js
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints Infinite Scroll Shortcut - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
!function(){"use strict";function t(n){this.options=i.extend({},t.defaults,n),this.container=this.options.element,"auto"!==this.options.container&&(this.container=this.options.container),this.$container=i(this.container),this.$more=i(this.options.more),this.$more.length&&(this.setupHandler(),this.waypoint=new o(this.options))}var i=window.jQuery,o=window.Waypoint;t.prototype.setupHandler=function(){this.options.handler=i.proxy(function(){this.options.onBeforePageLoad(),this.destroy(),this.$container.addClass(this.options.loadingClass),i.get(i(this.options.more).attr("href"),i.proxy(function(t){var n=i(i.parseHTML(t)),e=n.find(this.options.more),s=n.find(this.options.items);s.length||(s=n.filter(this.options.items)),this.$container.append(s),this.$container.removeClass(this.options.loadingClass),e.length||(e=n.filter(this.options.more)),e.length?(this.$more.replaceWith(e),this.$more=e,this.waypoint=new o(this.options)):this.$more.remove(),this.options.onAfterPageLoad()},this))},this)},t.prototype.destroy=function(){this.waypoint&&this.waypoint.destroy()},t.defaults={container:"auto",items:".infinite-item",more:".infinite-more-link",offset:"bottom-in-view",loadingClass:"infinite-loading",onBeforePageLoad:i.noop,onAfterPageLoad:i.noop},o.Infinite=t}();
|
103
static/libs/waypoints/lib/shortcuts/inview.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints Inview Shortcut - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function noop() {}
|
||||||
|
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/shortcuts/inview */
|
||||||
|
function Inview(options) {
|
||||||
|
this.options = Waypoint.Adapter.extend({}, Inview.defaults, options)
|
||||||
|
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
|
||||||
|
this.waypoints = []
|
||||||
|
this.createWaypoints()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Inview.prototype.createWaypoints = function() {
|
||||||
|
var configs = {
|
||||||
|
vertical: [{
|
||||||
|
down: 'enter',
|
||||||
|
up: 'exited',
|
||||||
|
offset: '100%'
|
||||||
|
}, {
|
||||||
|
down: 'entered',
|
||||||
|
up: 'exit',
|
||||||
|
offset: 'bottom-in-view'
|
||||||
|
}, {
|
||||||
|
down: 'exit',
|
||||||
|
up: 'entered',
|
||||||
|
offset: 0
|
||||||
|
}, {
|
||||||
|
down: 'exited',
|
||||||
|
up: 'enter',
|
||||||
|
offset: function() {
|
||||||
|
return -this.adapter.outerHeight()
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
horizontal: [{
|
||||||
|
right: 'enter',
|
||||||
|
left: 'exited',
|
||||||
|
offset: '100%'
|
||||||
|
}, {
|
||||||
|
right: 'entered',
|
||||||
|
left: 'exit',
|
||||||
|
offset: 'right-in-view'
|
||||||
|
}, {
|
||||||
|
right: 'exit',
|
||||||
|
left: 'entered',
|
||||||
|
offset: 0
|
||||||
|
}, {
|
||||||
|
right: 'exited',
|
||||||
|
left: 'enter',
|
||||||
|
offset: function() {
|
||||||
|
return -this.adapter.outerWidth()
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0, end = configs[this.axis].length; i < end; i++) {
|
||||||
|
var config = configs[this.axis][i]
|
||||||
|
this.createWaypoint(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Inview.prototype.createWaypoint = function(config) {
|
||||||
|
var self = this
|
||||||
|
this.waypoints.push(new Waypoint({
|
||||||
|
element: this.options.element,
|
||||||
|
handler: (function(config) {
|
||||||
|
return function(direction) {
|
||||||
|
self.options[config[direction]].call(this, direction)
|
||||||
|
}
|
||||||
|
}(config)),
|
||||||
|
offset: config.offset,
|
||||||
|
horizontal: this.options.horizontal
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
Inview.prototype.destroy = function() {
|
||||||
|
for (var i = 0, end = this.waypoints.length; i < end; i++) {
|
||||||
|
this.waypoints[i].destroy()
|
||||||
|
}
|
||||||
|
this.waypoints = []
|
||||||
|
}
|
||||||
|
|
||||||
|
Inview.defaults = {
|
||||||
|
enter: noop,
|
||||||
|
entered: noop,
|
||||||
|
exit: noop,
|
||||||
|
exited: noop
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.Inview = Inview
|
||||||
|
}())
|
||||||
|
;
|
7
static/libs/waypoints/lib/shortcuts/inview.min.js
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints Inview Shortcut - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
!function(){"use strict";function t(){}function e(t){this.options=i.Adapter.extend({},e.defaults,t),this.axis=this.options.horizontal?"horizontal":"vertical",this.waypoints=[],this.createWaypoints()}var i=window.Waypoint;e.prototype.createWaypoints=function(){for(var t={vertical:[{down:"enter",up:"exited",offset:"100%"},{down:"entered",up:"exit",offset:"bottom-in-view"},{down:"exit",up:"entered",offset:0},{down:"exited",up:"enter",offset:function(){return-this.adapter.outerHeight()}}],horizontal:[{right:"enter",left:"exited",offset:"100%"},{right:"entered",left:"exit",offset:"right-in-view"},{right:"exit",left:"entered",offset:0},{right:"exited",left:"enter",offset:function(){return-this.adapter.outerWidth()}}]},e=0,i=t[this.axis].length;i>e;e++){var o=t[this.axis][e];this.createWaypoint(o)}},e.prototype.createWaypoint=function(t){var e=this;this.waypoints.push(new i({element:this.options.element,handler:function(t){return function(i){e.options[t[i]].call(this,i)}}(t),offset:t.offset,horizontal:this.options.horizontal}))},e.prototype.destroy=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].destroy();this.waypoints=[]},e.defaults={enter:t,entered:t,exit:t,exited:t},i.Inview=e}();
|
65
static/libs/waypoints/lib/shortcuts/sticky.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints Sticky Element Shortcut - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var $ = window.jQuery
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/shortcuts/sticky-elements */
|
||||||
|
function Sticky(options) {
|
||||||
|
this.options = $.extend({}, Waypoint.defaults, Sticky.defaults, options)
|
||||||
|
this.element = this.options.element
|
||||||
|
this.$element = $(this.element)
|
||||||
|
this.createWrapper()
|
||||||
|
this.createWaypoint()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Sticky.prototype.createWaypoint = function() {
|
||||||
|
var originalHandler = this.options.handler
|
||||||
|
|
||||||
|
this.waypoint = new Waypoint($.extend({}, this.options, {
|
||||||
|
element: this.wrapper,
|
||||||
|
handler: $.proxy(function(direction) {
|
||||||
|
var shouldBeStuck = this.options.direction.indexOf(direction) > -1
|
||||||
|
var wrapperHeight = shouldBeStuck ? this.$element.outerHeight(true) : ''
|
||||||
|
|
||||||
|
this.$wrapper.height(wrapperHeight)
|
||||||
|
this.$element.toggleClass(this.options.stuckClass, shouldBeStuck)
|
||||||
|
|
||||||
|
if (originalHandler) {
|
||||||
|
originalHandler.call(this, direction)
|
||||||
|
}
|
||||||
|
}, this)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Sticky.prototype.createWrapper = function() {
|
||||||
|
this.$element.wrap(this.options.wrapper)
|
||||||
|
this.$wrapper = this.$element.parent()
|
||||||
|
this.wrapper = this.$wrapper[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
Sticky.prototype.destroy = function() {
|
||||||
|
if (this.$element.parent()[0] === this.wrapper) {
|
||||||
|
this.waypoint.destroy()
|
||||||
|
this.$element.removeClass(this.options.stuckClass).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sticky.defaults = {
|
||||||
|
wrapper: '<div class="sticky-wrapper" />',
|
||||||
|
stuckClass: 'stuck',
|
||||||
|
direction: 'down right'
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.Sticky = Sticky
|
||||||
|
}())
|
||||||
|
;
|
7
static/libs/waypoints/lib/shortcuts/sticky.min.js
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints Sticky Element Shortcut - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
!function(){"use strict";function t(s){this.options=e.extend({},i.defaults,t.defaults,s),this.element=this.options.element,this.$element=e(this.element),this.createWrapper(),this.createWaypoint()}var e=window.jQuery,i=window.Waypoint;t.prototype.createWaypoint=function(){var t=this.options.handler;this.waypoint=new i(e.extend({},this.options,{element:this.wrapper,handler:e.proxy(function(e){var i=this.options.direction.indexOf(e)>-1,s=i?this.$element.outerHeight(!0):"";this.$wrapper.height(s),this.$element.toggleClass(this.options.stuckClass,i),t&&t.call(this,e)},this)}))},t.prototype.createWrapper=function(){this.$element.wrap(this.options.wrapper),this.$wrapper=this.$element.parent(),this.wrapper=this.$wrapper[0]},t.prototype.destroy=function(){this.$element.parent()[0]===this.wrapper&&(this.waypoint.destroy(),this.$element.removeClass(this.options.stuckClass).unwrap())},t.defaults={wrapper:'<div class="sticky-wrapper" />',stuckClass:"stuck",direction:"down right"},i.Sticky=t}();
|
46
static/libs/waypoints/lib/waypoints.debug.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints Debug - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var displayNoneMessage = [
|
||||||
|
'You have a Waypoint element with display none. For more information on ',
|
||||||
|
'why this is a bad idea read ',
|
||||||
|
'http://imakewebthings.com/waypoints/guides/debugging/#display-none'
|
||||||
|
].join('')
|
||||||
|
var fixedMessage = [
|
||||||
|
'You have a Waypoint element with fixed positioning. For more ',
|
||||||
|
'information on why this is a bad idea read ',
|
||||||
|
'http://imakewebthings.com/waypoints/guides/debugging/#fixed-position'
|
||||||
|
].join('')
|
||||||
|
|
||||||
|
function checkWaypointStyles() {
|
||||||
|
var originalRefresh = window.Waypoint.Context.prototype.refresh
|
||||||
|
|
||||||
|
window.Waypoint.Context.prototype.refresh = function() {
|
||||||
|
for (var axis in this.waypoints) {
|
||||||
|
for (var key in this.waypoints[axis]) {
|
||||||
|
var waypoint = this.waypoints[axis][key]
|
||||||
|
var style = window.getComputedStyle(waypoint.element)
|
||||||
|
if (!waypoint.enabled) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (style && style.display === 'none') {
|
||||||
|
console.error(displayNoneMessage)
|
||||||
|
}
|
||||||
|
if (style && style.position === 'fixed') {
|
||||||
|
console.error(fixedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return originalRefresh.call(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkWaypointStyles()
|
||||||
|
}())
|
||||||
|
;
|
690
static/libs/waypoints/lib/zepto.waypoints.js
Normal file
@ -0,0 +1,690 @@
|
|||||||
|
/*!
|
||||||
|
Waypoints - 3.1.1
|
||||||
|
Copyright © 2011-2015 Caleb Troughton
|
||||||
|
Licensed under the MIT license.
|
||||||
|
https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var keyCounter = 0
|
||||||
|
var allWaypoints = {}
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/waypoint */
|
||||||
|
function Waypoint(options) {
|
||||||
|
if (!options) {
|
||||||
|
throw new Error('No options passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
if (!options.element) {
|
||||||
|
throw new Error('No element option passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
if (!options.handler) {
|
||||||
|
throw new Error('No handler option passed to Waypoint constructor')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.key = 'waypoint-' + keyCounter
|
||||||
|
this.options = Waypoint.Adapter.extend({}, Waypoint.defaults, options)
|
||||||
|
this.element = this.options.element
|
||||||
|
this.adapter = new Waypoint.Adapter(this.element)
|
||||||
|
this.callback = options.handler
|
||||||
|
this.axis = this.options.horizontal ? 'horizontal' : 'vertical'
|
||||||
|
this.enabled = this.options.enabled
|
||||||
|
this.triggerPoint = null
|
||||||
|
this.group = Waypoint.Group.findOrCreate({
|
||||||
|
name: this.options.group,
|
||||||
|
axis: this.axis
|
||||||
|
})
|
||||||
|
this.context = Waypoint.Context.findOrCreateByElement(this.options.context)
|
||||||
|
|
||||||
|
if (Waypoint.offsetAliases[this.options.offset]) {
|
||||||
|
this.options.offset = Waypoint.offsetAliases[this.options.offset]
|
||||||
|
}
|
||||||
|
this.group.add(this)
|
||||||
|
this.context.add(this)
|
||||||
|
allWaypoints[this.key] = this
|
||||||
|
keyCounter += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.prototype.queueTrigger = function(direction) {
|
||||||
|
this.group.queueTrigger(this, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.prototype.trigger = function(args) {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.callback) {
|
||||||
|
this.callback.apply(this, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/destroy */
|
||||||
|
Waypoint.prototype.destroy = function() {
|
||||||
|
this.context.remove(this)
|
||||||
|
this.group.remove(this)
|
||||||
|
delete allWaypoints[this.key]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/disable */
|
||||||
|
Waypoint.prototype.disable = function() {
|
||||||
|
this.enabled = false
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/enable */
|
||||||
|
Waypoint.prototype.enable = function() {
|
||||||
|
this.context.refresh()
|
||||||
|
this.enabled = true
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/next */
|
||||||
|
Waypoint.prototype.next = function() {
|
||||||
|
return this.group.next(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/previous */
|
||||||
|
Waypoint.prototype.previous = function() {
|
||||||
|
return this.group.previous(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Waypoint.invokeAll = function(method) {
|
||||||
|
var allWaypointsArray = []
|
||||||
|
for (var waypointKey in allWaypoints) {
|
||||||
|
allWaypointsArray.push(allWaypoints[waypointKey])
|
||||||
|
}
|
||||||
|
for (var i = 0, end = allWaypointsArray.length; i < end; i++) {
|
||||||
|
allWaypointsArray[i][method]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/destroy-all */
|
||||||
|
Waypoint.destroyAll = function() {
|
||||||
|
Waypoint.invokeAll('destroy')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/disable-all */
|
||||||
|
Waypoint.disableAll = function() {
|
||||||
|
Waypoint.invokeAll('disable')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/enable-all */
|
||||||
|
Waypoint.enableAll = function() {
|
||||||
|
Waypoint.invokeAll('enable')
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/refresh-all */
|
||||||
|
Waypoint.refreshAll = function() {
|
||||||
|
Waypoint.Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/viewport-height */
|
||||||
|
Waypoint.viewportHeight = function() {
|
||||||
|
return window.innerHeight || document.documentElement.clientHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/viewport-width */
|
||||||
|
Waypoint.viewportWidth = function() {
|
||||||
|
return document.documentElement.clientWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.adapters = []
|
||||||
|
|
||||||
|
Waypoint.defaults = {
|
||||||
|
context: window,
|
||||||
|
continuous: true,
|
||||||
|
enabled: true,
|
||||||
|
group: 'default',
|
||||||
|
horizontal: false,
|
||||||
|
offset: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.offsetAliases = {
|
||||||
|
'bottom-in-view': function() {
|
||||||
|
return this.context.innerHeight() - this.adapter.outerHeight()
|
||||||
|
},
|
||||||
|
'right-in-view': function() {
|
||||||
|
return this.context.innerWidth() - this.adapter.outerWidth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Waypoint = Waypoint
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function requestAnimationFrameShim(callback) {
|
||||||
|
window.setTimeout(callback, 1000 / 60)
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyCounter = 0
|
||||||
|
var contexts = {}
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
var oldWindowLoad = window.onload
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context */
|
||||||
|
function Context(element) {
|
||||||
|
this.element = element
|
||||||
|
this.Adapter = Waypoint.Adapter
|
||||||
|
this.adapter = new this.Adapter(element)
|
||||||
|
this.key = 'waypoint-context-' + keyCounter
|
||||||
|
this.didScroll = false
|
||||||
|
this.didResize = false
|
||||||
|
this.oldScroll = {
|
||||||
|
x: this.adapter.scrollLeft(),
|
||||||
|
y: this.adapter.scrollTop()
|
||||||
|
}
|
||||||
|
this.waypoints = {
|
||||||
|
vertical: {},
|
||||||
|
horizontal: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
element.waypointContextKey = this.key
|
||||||
|
contexts[element.waypointContextKey] = this
|
||||||
|
keyCounter += 1
|
||||||
|
|
||||||
|
this.createThrottledScrollHandler()
|
||||||
|
this.createThrottledResizeHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.add = function(waypoint) {
|
||||||
|
var axis = waypoint.options.horizontal ? 'horizontal' : 'vertical'
|
||||||
|
this.waypoints[axis][waypoint.key] = waypoint
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.checkEmpty = function() {
|
||||||
|
var horizontalEmpty = this.Adapter.isEmptyObject(this.waypoints.horizontal)
|
||||||
|
var verticalEmpty = this.Adapter.isEmptyObject(this.waypoints.vertical)
|
||||||
|
if (horizontalEmpty && verticalEmpty) {
|
||||||
|
this.adapter.off('.waypoints')
|
||||||
|
delete contexts[this.key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.createThrottledResizeHandler = function() {
|
||||||
|
var self = this
|
||||||
|
|
||||||
|
function resizeHandler() {
|
||||||
|
self.handleResize()
|
||||||
|
self.didResize = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adapter.on('resize.waypoints', function() {
|
||||||
|
if (!self.didResize) {
|
||||||
|
self.didResize = true
|
||||||
|
Waypoint.requestAnimationFrame(resizeHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.createThrottledScrollHandler = function() {
|
||||||
|
var self = this
|
||||||
|
function scrollHandler() {
|
||||||
|
self.handleScroll()
|
||||||
|
self.didScroll = false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.adapter.on('scroll.waypoints', function() {
|
||||||
|
if (!self.didScroll || Waypoint.isTouch) {
|
||||||
|
self.didScroll = true
|
||||||
|
Waypoint.requestAnimationFrame(scrollHandler)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.handleResize = function() {
|
||||||
|
Waypoint.Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.handleScroll = function() {
|
||||||
|
var triggeredGroups = {}
|
||||||
|
var axes = {
|
||||||
|
horizontal: {
|
||||||
|
newScroll: this.adapter.scrollLeft(),
|
||||||
|
oldScroll: this.oldScroll.x,
|
||||||
|
forward: 'right',
|
||||||
|
backward: 'left'
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
newScroll: this.adapter.scrollTop(),
|
||||||
|
oldScroll: this.oldScroll.y,
|
||||||
|
forward: 'down',
|
||||||
|
backward: 'up'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var axisKey in axes) {
|
||||||
|
var axis = axes[axisKey]
|
||||||
|
var isForward = axis.newScroll > axis.oldScroll
|
||||||
|
var direction = isForward ? axis.forward : axis.backward
|
||||||
|
|
||||||
|
for (var waypointKey in this.waypoints[axisKey]) {
|
||||||
|
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||||
|
var wasBeforeTriggerPoint = axis.oldScroll < waypoint.triggerPoint
|
||||||
|
var nowAfterTriggerPoint = axis.newScroll >= waypoint.triggerPoint
|
||||||
|
var crossedForward = wasBeforeTriggerPoint && nowAfterTriggerPoint
|
||||||
|
var crossedBackward = !wasBeforeTriggerPoint && !nowAfterTriggerPoint
|
||||||
|
if (crossedForward || crossedBackward) {
|
||||||
|
waypoint.queueTrigger(direction)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var groupKey in triggeredGroups) {
|
||||||
|
triggeredGroups[groupKey].flushTriggers()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oldScroll = {
|
||||||
|
x: axes.horizontal.newScroll,
|
||||||
|
y: axes.vertical.newScroll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.innerHeight = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
if (this.element == this.element.window) {
|
||||||
|
return Waypoint.viewportHeight()
|
||||||
|
}
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
return this.adapter.innerHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.remove = function(waypoint) {
|
||||||
|
delete this.waypoints[waypoint.axis][waypoint.key]
|
||||||
|
this.checkEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.prototype.innerWidth = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
if (this.element == this.element.window) {
|
||||||
|
return Waypoint.viewportWidth()
|
||||||
|
}
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
return this.adapter.innerWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-destroy */
|
||||||
|
Context.prototype.destroy = function() {
|
||||||
|
var allWaypoints = []
|
||||||
|
for (var axis in this.waypoints) {
|
||||||
|
for (var waypointKey in this.waypoints[axis]) {
|
||||||
|
allWaypoints.push(this.waypoints[axis][waypointKey])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0, end = allWaypoints.length; i < end; i++) {
|
||||||
|
allWaypoints[i].destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-refresh */
|
||||||
|
Context.prototype.refresh = function() {
|
||||||
|
/*eslint-disable eqeqeq */
|
||||||
|
var isWindow = this.element == this.element.window
|
||||||
|
/*eslint-enable eqeqeq */
|
||||||
|
var contextOffset = this.adapter.offset()
|
||||||
|
var triggeredGroups = {}
|
||||||
|
var axes
|
||||||
|
|
||||||
|
this.handleScroll()
|
||||||
|
axes = {
|
||||||
|
horizontal: {
|
||||||
|
contextOffset: isWindow ? 0 : contextOffset.left,
|
||||||
|
contextScroll: isWindow ? 0 : this.oldScroll.x,
|
||||||
|
contextDimension: this.innerWidth(),
|
||||||
|
oldScroll: this.oldScroll.x,
|
||||||
|
forward: 'right',
|
||||||
|
backward: 'left',
|
||||||
|
offsetProp: 'left'
|
||||||
|
},
|
||||||
|
vertical: {
|
||||||
|
contextOffset: isWindow ? 0 : contextOffset.top,
|
||||||
|
contextScroll: isWindow ? 0 : this.oldScroll.y,
|
||||||
|
contextDimension: this.innerHeight(),
|
||||||
|
oldScroll: this.oldScroll.y,
|
||||||
|
forward: 'down',
|
||||||
|
backward: 'up',
|
||||||
|
offsetProp: 'top'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var axisKey in axes) {
|
||||||
|
var axis = axes[axisKey]
|
||||||
|
for (var waypointKey in this.waypoints[axisKey]) {
|
||||||
|
var waypoint = this.waypoints[axisKey][waypointKey]
|
||||||
|
var adjustment = waypoint.options.offset
|
||||||
|
var oldTriggerPoint = waypoint.triggerPoint
|
||||||
|
var elementOffset = 0
|
||||||
|
var freshWaypoint = oldTriggerPoint == null
|
||||||
|
var contextModifier, wasBeforeScroll, nowAfterScroll
|
||||||
|
var triggeredBackward, triggeredForward
|
||||||
|
|
||||||
|
if (waypoint.element !== waypoint.element.window) {
|
||||||
|
elementOffset = waypoint.adapter.offset()[axis.offsetProp]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof adjustment === 'function') {
|
||||||
|
adjustment = adjustment.apply(waypoint)
|
||||||
|
}
|
||||||
|
else if (typeof adjustment === 'string') {
|
||||||
|
adjustment = parseFloat(adjustment)
|
||||||
|
if (waypoint.options.offset.indexOf('%') > - 1) {
|
||||||
|
adjustment = Math.ceil(axis.contextDimension * adjustment / 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contextModifier = axis.contextScroll - axis.contextOffset
|
||||||
|
waypoint.triggerPoint = elementOffset + contextModifier - adjustment
|
||||||
|
wasBeforeScroll = oldTriggerPoint < axis.oldScroll
|
||||||
|
nowAfterScroll = waypoint.triggerPoint >= axis.oldScroll
|
||||||
|
triggeredBackward = wasBeforeScroll && nowAfterScroll
|
||||||
|
triggeredForward = !wasBeforeScroll && !nowAfterScroll
|
||||||
|
|
||||||
|
if (!freshWaypoint && triggeredBackward) {
|
||||||
|
waypoint.queueTrigger(axis.backward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
else if (!freshWaypoint && triggeredForward) {
|
||||||
|
waypoint.queueTrigger(axis.forward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
else if (freshWaypoint && axis.oldScroll >= waypoint.triggerPoint) {
|
||||||
|
waypoint.queueTrigger(axis.forward)
|
||||||
|
triggeredGroups[waypoint.group.id] = waypoint.group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var groupKey in triggeredGroups) {
|
||||||
|
triggeredGroups[groupKey].flushTriggers()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.findOrCreateByElement = function(element) {
|
||||||
|
return Context.findByElement(element) || new Context(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Context.refreshAll = function() {
|
||||||
|
for (var contextId in contexts) {
|
||||||
|
contexts[contextId].refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/context-find-by-element */
|
||||||
|
Context.findByElement = function(element) {
|
||||||
|
return contexts[element.waypointContextKey]
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
if (oldWindowLoad) {
|
||||||
|
oldWindowLoad()
|
||||||
|
}
|
||||||
|
Context.refreshAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.requestAnimationFrame = function(callback) {
|
||||||
|
var requestFn = window.requestAnimationFrame ||
|
||||||
|
window.mozRequestAnimationFrame ||
|
||||||
|
window.webkitRequestAnimationFrame ||
|
||||||
|
requestAnimationFrameShim
|
||||||
|
requestFn.call(window, callback)
|
||||||
|
}
|
||||||
|
Waypoint.Context = Context
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
function byTriggerPoint(a, b) {
|
||||||
|
return a.triggerPoint - b.triggerPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
function byReverseTriggerPoint(a, b) {
|
||||||
|
return b.triggerPoint - a.triggerPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
var groups = {
|
||||||
|
vertical: {},
|
||||||
|
horizontal: {}
|
||||||
|
}
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
/* http://imakewebthings.com/waypoints/api/group */
|
||||||
|
function Group(options) {
|
||||||
|
this.name = options.name
|
||||||
|
this.axis = options.axis
|
||||||
|
this.id = this.name + '-' + this.axis
|
||||||
|
this.waypoints = []
|
||||||
|
this.clearTriggerQueues()
|
||||||
|
groups[this.axis][this.name] = this
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.add = function(waypoint) {
|
||||||
|
this.waypoints.push(waypoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.clearTriggerQueues = function() {
|
||||||
|
this.triggerQueues = {
|
||||||
|
up: [],
|
||||||
|
down: [],
|
||||||
|
left: [],
|
||||||
|
right: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.flushTriggers = function() {
|
||||||
|
for (var direction in this.triggerQueues) {
|
||||||
|
var waypoints = this.triggerQueues[direction]
|
||||||
|
var reverse = direction === 'up' || direction === 'left'
|
||||||
|
waypoints.sort(reverse ? byReverseTriggerPoint : byTriggerPoint)
|
||||||
|
for (var i = 0, end = waypoints.length; i < end; i += 1) {
|
||||||
|
var waypoint = waypoints[i]
|
||||||
|
if (waypoint.options.continuous || i === waypoints.length - 1) {
|
||||||
|
waypoint.trigger([direction])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.clearTriggerQueues()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.next = function(waypoint) {
|
||||||
|
this.waypoints.sort(byTriggerPoint)
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
var isLast = index === this.waypoints.length - 1
|
||||||
|
return isLast ? null : this.waypoints[index + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.previous = function(waypoint) {
|
||||||
|
this.waypoints.sort(byTriggerPoint)
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
return index ? this.waypoints[index - 1] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.queueTrigger = function(waypoint, direction) {
|
||||||
|
this.triggerQueues[direction].push(waypoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.prototype.remove = function(waypoint) {
|
||||||
|
var index = Waypoint.Adapter.inArray(waypoint, this.waypoints)
|
||||||
|
if (index > -1) {
|
||||||
|
this.waypoints.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/first */
|
||||||
|
Group.prototype.first = function() {
|
||||||
|
return this.waypoints[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
/* http://imakewebthings.com/waypoints/api/last */
|
||||||
|
Group.prototype.last = function() {
|
||||||
|
return this.waypoints[this.waypoints.length - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
Group.findOrCreate = function(options) {
|
||||||
|
return groups[options.axis][options.name] || new Group(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.Group = Group
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var $ = window.Zepto
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
function ZeptoAdapter(element) {
|
||||||
|
this.element = element
|
||||||
|
this.$element = $(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each([
|
||||||
|
'off',
|
||||||
|
'on',
|
||||||
|
'scrollLeft',
|
||||||
|
'scrollTop'
|
||||||
|
], function(i, method) {
|
||||||
|
ZeptoAdapter.prototype[method] = function() {
|
||||||
|
var args = Array.prototype.slice.call(arguments)
|
||||||
|
return this.$element[method].apply(this.$element, args)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ZeptoAdapter.prototype.offset = function() {
|
||||||
|
if (this.element !== this.element.window) {
|
||||||
|
return this.$element.offset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from https://gist.github.com/wheresrhys/5823198
|
||||||
|
$.each([
|
||||||
|
'width',
|
||||||
|
'height'
|
||||||
|
], function(i, dimension) {
|
||||||
|
function createDimensionMethod($element, includeBorder) {
|
||||||
|
return function(includeMargin) {
|
||||||
|
var $element = this.$element
|
||||||
|
var size = $element[dimension]()
|
||||||
|
var sides = {
|
||||||
|
width: ['left', 'right'],
|
||||||
|
height: ['top', 'bottom']
|
||||||
|
}
|
||||||
|
|
||||||
|
$.each(sides[dimension], function(i, side) {
|
||||||
|
size += parseInt($element.css('padding-' + side), 10)
|
||||||
|
if (includeBorder) {
|
||||||
|
size += parseInt($element.css('border-' + side + '-width'), 10)
|
||||||
|
}
|
||||||
|
if (includeMargin) {
|
||||||
|
size += parseInt($element.css('margin-' + side), 10)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var innerMethod = $.camelCase('inner-' + dimension)
|
||||||
|
var outerMethod = $.camelCase('outer-' + dimension)
|
||||||
|
|
||||||
|
ZeptoAdapter.prototype[innerMethod] = createDimensionMethod(false)
|
||||||
|
ZeptoAdapter.prototype[outerMethod] = createDimensionMethod(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
$.each([
|
||||||
|
'extend',
|
||||||
|
'inArray'
|
||||||
|
], function(i, method) {
|
||||||
|
ZeptoAdapter[method] = $[method]
|
||||||
|
})
|
||||||
|
|
||||||
|
ZeptoAdapter.isEmptyObject = function(obj) {
|
||||||
|
/* eslint no-unused-vars: 0 */
|
||||||
|
for (var name in obj) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
Waypoint.adapters.push({
|
||||||
|
name: 'zepto',
|
||||||
|
Adapter: ZeptoAdapter
|
||||||
|
})
|
||||||
|
Waypoint.Adapter = ZeptoAdapter
|
||||||
|
}())
|
||||||
|
;(function() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
var Waypoint = window.Waypoint
|
||||||
|
|
||||||
|
function createExtension(framework) {
|
||||||
|
return function() {
|
||||||
|
var waypoints = []
|
||||||
|
var overrides = arguments[0]
|
||||||
|
|
||||||
|
if (framework.isFunction(arguments[0])) {
|
||||||
|
overrides = framework.extend({}, arguments[1])
|
||||||
|
overrides.handler = arguments[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
this.each(function() {
|
||||||
|
var options = framework.extend({}, overrides, {
|
||||||
|
element: this
|
||||||
|
})
|
||||||
|
if (typeof options.context === 'string') {
|
||||||
|
options.context = framework(this).closest(options.context)[0]
|
||||||
|
}
|
||||||
|
waypoints.push(new Waypoint(options))
|
||||||
|
})
|
||||||
|
|
||||||
|
return waypoints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.jQuery) {
|
||||||
|
window.jQuery.fn.waypoint = createExtension(window.jQuery)
|
||||||
|
}
|
||||||
|
if (window.Zepto) {
|
||||||
|
window.Zepto.fn.waypoint = createExtension(window.Zepto)
|
||||||
|
}
|
||||||
|
}())
|
||||||
|
;
|
7
static/libs/waypoints/lib/zepto.waypoints.min.js
vendored
Normal file
69
utils/ip/ip.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package ip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"HFish/error"
|
||||||
|
"io/ioutil"
|
||||||
|
"github.com/axgle/mahonia"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"net"
|
||||||
|
"fmt"
|
||||||
|
"HFish/utils/try"
|
||||||
|
"HFish/utils/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 爬虫 ip138 获取 ip 地理信息
|
||||||
|
func Get(ip string) string {
|
||||||
|
result := ""
|
||||||
|
try.Try(func() {
|
||||||
|
resp, err := http.Get("http://ip138.com/ips138.asp?ip=" + ip)
|
||||||
|
error.Check(err, "请求IP138异常")
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
input, err := ioutil.ReadAll(resp.Body)
|
||||||
|
error.Check(err, "读取IP138内容异常")
|
||||||
|
|
||||||
|
out := mahonia.NewDecoder("gbk").ConvertString(string(input))
|
||||||
|
|
||||||
|
reg := regexp.MustCompile(`<ul class="ul1"><li>\W*`)
|
||||||
|
arr := reg.FindAllString(string(out), -1)
|
||||||
|
str1 := strings.Replace(arr[0], `<ul class="ul1"><li>本站数据:`, "", -1)
|
||||||
|
str2 := strings.Replace(str1, `</`, "", -1)
|
||||||
|
str3 := strings.Replace(str2, ` `, "", -1)
|
||||||
|
str4 := strings.Replace(str3, " ", "", -1)
|
||||||
|
result = strings.Replace(str4, "\n", "", -1)
|
||||||
|
|
||||||
|
if result == "保留地址" {
|
||||||
|
result = "本地IP"
|
||||||
|
}
|
||||||
|
|
||||||
|
}).Catch(func() {
|
||||||
|
log.Pr("IP138", "127.0.0.1", "读取 ip138 内容异常")
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLocalIp() string {
|
||||||
|
netInterfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("net.Interfaces failed, err:", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(netInterfaces); i++ {
|
||||||
|
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
||||||
|
addrs, _ := netInterfaces[i].Addrs()
|
||||||
|
|
||||||
|
for _, address := range addrs {
|
||||||
|
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||||
|
if ipnet.IP.To4() != nil {
|
||||||
|
return ipnet.IP.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
16
utils/is/is.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package is
|
||||||
|
|
||||||
|
import (
|
||||||
|
"HFish/utils/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Rpc() bool {
|
||||||
|
rpcStatus := conf.Get("rpc", "status")
|
||||||
|
|
||||||
|
if rpcStatus == "2" {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
utils/log/log.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Pr(typex string, ip string, text string, a ...interface{}) {
|
||||||
|
fmt.Fprintln(gin.DefaultWriter, "["+typex+"] "+ip+" - ["+time.Now().Format("2006-01-02 15:04:05")+"] "+text+" ", a)
|
||||||
|
}
|
21
utils/page/page.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package page
|
||||||
|
|
||||||
|
// 分页从1开始
|
||||||
|
func Start(currentPage int64, pageSize int64) int64 {
|
||||||
|
return (currentPage - 1) * pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页结束
|
||||||
|
func End(currentPage int64, pageSize int64) int64 {
|
||||||
|
return currentPage * pageSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页总页数
|
||||||
|
func TotalPage(count int64, pageSize int64) int64 {
|
||||||
|
result := count / pageSize
|
||||||
|
yu := count % pageSize
|
||||||
|
if yu > 0 {
|
||||||
|
result = result + 1
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
@ -1,11 +1,9 @@
|
|||||||
package send
|
package send
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"gopkg.in/gomail.v2"
|
"gopkg.in/gomail.v2"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"HFish/utils/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SendMail(mailTo []string, subject string, body string, config []string) error {
|
func SendMail(mailTo []string, subject string, body string, config []string) error {
|
||||||
@ -22,9 +20,9 @@ func SendMail(mailTo []string, subject string, body string, config []string) err
|
|||||||
err := d.DialAndSend(m)
|
err := d.DialAndSend(m)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(gin.DefaultWriter, time.Now().Format("2006-01-02 15:04:05")+" 发送邮件通知失败 ", err)
|
log.Pr("HFish", "127.0.0.1", "发送邮件通知失败", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(gin.DefaultWriter, time.Now().Format("2006-01-02 15:04:05")+" 发送邮件通知成功")
|
log.Pr("HFish", "127.0.0.1", "发送邮件通知成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -14,9 +14,13 @@ import (
|
|||||||
"HFish/core/protocol/ssh"
|
"HFish/core/protocol/ssh"
|
||||||
"HFish/core/protocol/redis"
|
"HFish/core/protocol/redis"
|
||||||
"HFish/core/protocol/mysql"
|
"HFish/core/protocol/mysql"
|
||||||
|
"HFish/core/protocol/ftp"
|
||||||
|
"HFish/core/protocol/telnet"
|
||||||
|
"HFish/core/rpc/server"
|
||||||
|
"HFish/core/rpc/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunWeb(template string, static string, url string) http.Handler {
|
func RunWeb(template string, index string, static string, url string) http.Handler {
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.Use(gin.Recovery())
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
@ -27,7 +31,24 @@ func RunWeb(template string, static string, url string) http.Handler {
|
|||||||
r.Static("/static", "./web/"+static)
|
r.Static("/static", "./web/"+static)
|
||||||
|
|
||||||
r.GET(url, func(c *gin.Context) {
|
r.GET(url, func(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "index.html", gin.H{})
|
c.HTML(http.StatusOK, index, gin.H{})
|
||||||
|
})
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunDeep(template string, index string, static string, url string) http.Handler {
|
||||||
|
r := gin.New()
|
||||||
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
// 引入html资源
|
||||||
|
r.LoadHTMLGlob("web/" + template + "/*")
|
||||||
|
|
||||||
|
// 引入静态资源
|
||||||
|
r.Static("/static", "./web/"+static)
|
||||||
|
|
||||||
|
r.GET(url, func(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, index, gin.H{})
|
||||||
})
|
})
|
||||||
|
|
||||||
return r
|
return r
|
||||||
@ -35,12 +56,29 @@ func RunWeb(template string, static string, url string) http.Handler {
|
|||||||
|
|
||||||
func RunAdmin() http.Handler {
|
func RunAdmin() http.Handler {
|
||||||
gin.DisableConsoleColor()
|
gin.DisableConsoleColor()
|
||||||
|
|
||||||
f, _ := os.Create("./logs/hfish.log")
|
f, _ := os.Create("./logs/hfish.log")
|
||||||
gin.DefaultWriter = io.MultiWriter(f)
|
gin.DefaultWriter = io.MultiWriter(f)
|
||||||
|
|
||||||
// 引入gin
|
// 引入gin
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
|
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
|
||||||
|
return fmt.Sprintf("[HFish] %s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
|
||||||
|
param.ClientIP,
|
||||||
|
param.TimeStamp.Format("2006-01-02 15:04:05"),
|
||||||
|
param.Method,
|
||||||
|
param.Path,
|
||||||
|
param.Request.Proto,
|
||||||
|
param.StatusCode,
|
||||||
|
param.Latency,
|
||||||
|
param.Request.UserAgent(),
|
||||||
|
param.ErrorMessage,
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
|
||||||
r.Use(gin.Recovery())
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
// 引入html资源
|
// 引入html资源
|
||||||
r.LoadHTMLGlob("admin/*")
|
r.LoadHTMLGlob("admin/*")
|
||||||
|
|
||||||
@ -54,10 +92,43 @@ func RunAdmin() http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Run() {
|
func Run() {
|
||||||
// 启动 Mysql 钓鱼
|
// 启动 FTP 蜜罐
|
||||||
|
ftpStatus := conf.Get("ftp", "status")
|
||||||
|
|
||||||
|
// 判断 FTP 蜜罐 是否开启
|
||||||
|
if ftpStatus == "1" {
|
||||||
|
ftpAddr := conf.Get("ftp", "addr")
|
||||||
|
go ftp.Start(ftpAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
// 启动 Telnet 蜜罐
|
||||||
|
telnetStatus := conf.Get("telnet", "status")
|
||||||
|
|
||||||
|
// 判断 Telnet 蜜罐 是否开启
|
||||||
|
if telnetStatus == "1" {
|
||||||
|
telnetAddr := conf.Get("telnet", "addr")
|
||||||
|
go telnet.Start(telnetAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
//// 启动 HTTP 正向代理
|
||||||
|
//httpStatus := conf.Get("http", "status")
|
||||||
|
//
|
||||||
|
//// 判断 HTTP 正向代理 是否开启
|
||||||
|
//if httpStatus == "1" {
|
||||||
|
// httpAddr := conf.Get("http", "addr")
|
||||||
|
// go httpx.Start(httpAddr)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
// 启动 Mysql 蜜罐
|
||||||
mysqlStatus := conf.Get("mysql", "status")
|
mysqlStatus := conf.Get("mysql", "status")
|
||||||
|
|
||||||
// 判断 Mysql 钓鱼 是否开启
|
// 判断 Mysql 蜜罐 是否开启
|
||||||
if mysqlStatus == "1" {
|
if mysqlStatus == "1" {
|
||||||
mysqlAddr := conf.Get("mysql", "addr")
|
mysqlAddr := conf.Get("mysql", "addr")
|
||||||
|
|
||||||
@ -69,10 +140,10 @@ func Run() {
|
|||||||
|
|
||||||
//=========================//
|
//=========================//
|
||||||
|
|
||||||
// 启动 Redis 钓鱼
|
// 启动 Redis 蜜罐
|
||||||
redisStatus := conf.Get("redis", "status")
|
redisStatus := conf.Get("redis", "status")
|
||||||
|
|
||||||
// 判断 Redis 钓鱼 是否开启
|
// 判断 Redis 蜜罐 是否开启
|
||||||
if redisStatus == "1" {
|
if redisStatus == "1" {
|
||||||
redisAddr := conf.Get("redis", "addr")
|
redisAddr := conf.Get("redis", "addr")
|
||||||
go redis.Start(redisAddr)
|
go redis.Start(redisAddr)
|
||||||
@ -80,10 +151,10 @@ func Run() {
|
|||||||
|
|
||||||
//=========================//
|
//=========================//
|
||||||
|
|
||||||
// 启动 SSH 钓鱼
|
// 启动 SSH 蜜罐
|
||||||
sshStatus := conf.Get("ssh", "status")
|
sshStatus := conf.Get("ssh", "status")
|
||||||
|
|
||||||
// 判断 SSG 钓鱼 是否开启
|
// 判断 SSG 蜜罐 是否开启
|
||||||
if sshStatus == "1" {
|
if sshStatus == "1" {
|
||||||
sshAddr := conf.Get("ssh", "addr")
|
sshAddr := conf.Get("ssh", "addr")
|
||||||
go ssh.Start(sshAddr)
|
go ssh.Start(sshAddr)
|
||||||
@ -91,19 +162,20 @@ func Run() {
|
|||||||
|
|
||||||
//=========================//
|
//=========================//
|
||||||
|
|
||||||
// 启动 Web 钓鱼
|
// 启动 Web 蜜罐
|
||||||
webStatus := conf.Get("web", "status")
|
webStatus := conf.Get("web", "status")
|
||||||
|
|
||||||
// 判断 Web 钓鱼 是否开启
|
// 判断 Web 蜜罐 是否开启
|
||||||
if webStatus == "1" {
|
if webStatus == "1" {
|
||||||
webAddr := conf.Get("web", "addr")
|
webAddr := conf.Get("web", "addr")
|
||||||
webTemplate := conf.Get("web", "template")
|
webTemplate := conf.Get("web", "template")
|
||||||
webStatic := conf.Get("web", "static")
|
webStatic := conf.Get("web", "static")
|
||||||
webUrl := conf.Get("web", "url")
|
webUrl := conf.Get("web", "url")
|
||||||
|
webIndex := conf.Get("web", "index")
|
||||||
|
|
||||||
serverWeb := &http.Server{
|
serverWeb := &http.Server{
|
||||||
Addr: webAddr,
|
Addr: webAddr,
|
||||||
Handler: RunWeb(webTemplate, webStatic, webUrl),
|
Handler: RunWeb(webTemplate, webIndex, webStatic, webUrl),
|
||||||
ReadTimeout: 5 * time.Second,
|
ReadTimeout: 5 * time.Second,
|
||||||
WriteTimeout: 10 * time.Second,
|
WriteTimeout: 10 * time.Second,
|
||||||
}
|
}
|
||||||
@ -113,11 +185,58 @@ func Run() {
|
|||||||
|
|
||||||
//=========================//
|
//=========================//
|
||||||
|
|
||||||
|
// 启动 暗网 蜜罐
|
||||||
|
deepStatus := conf.Get("deep", "status")
|
||||||
|
|
||||||
|
// 判断 暗网 Web 蜜罐 是否开启
|
||||||
|
if deepStatus == "1" {
|
||||||
|
deepAddr := conf.Get("deep", "addr")
|
||||||
|
deepTemplate := conf.Get("deep", "template")
|
||||||
|
deepStatic := conf.Get("deep", "static")
|
||||||
|
deepkUrl := conf.Get("deep", "url")
|
||||||
|
deepIndex := conf.Get("deep", "index")
|
||||||
|
|
||||||
|
serverDark := &http.Server{
|
||||||
|
Addr: deepAddr,
|
||||||
|
Handler: RunDeep(deepTemplate, deepIndex, deepStatic, deepkUrl),
|
||||||
|
ReadTimeout: 5 * time.Second,
|
||||||
|
WriteTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
go serverDark.ListenAndServe()
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
|
||||||
|
// 启动 RPC
|
||||||
|
rpcStatus := conf.Get("rpc", "status")
|
||||||
|
|
||||||
|
// 判断 RPC 是否开启 1 RPC 服务端 2 RPC 客户端
|
||||||
|
if rpcStatus == "1" {
|
||||||
|
// 服务端监听地址
|
||||||
|
rpcAddr := conf.Get("rpc", "addr")
|
||||||
|
go server.Start(rpcAddr)
|
||||||
|
} else if rpcStatus == "2" {
|
||||||
|
// 客户端连接服务端
|
||||||
|
// 阻止进程,不启动 admin
|
||||||
|
|
||||||
|
rpcName := conf.Get("rpc", "name")
|
||||||
|
|
||||||
|
for {
|
||||||
|
// 这样写 提高IO读写性能
|
||||||
|
go client.Start(rpcName, ftpStatus, telnetStatus, "0", mysqlStatus, redisStatus, sshStatus, webStatus, deepStatus)
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(1) * time.Minute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================//
|
||||||
|
|
||||||
// 启动 admin 管理后台
|
// 启动 admin 管理后台
|
||||||
adminbAddr := conf.Get("admin", "addr")
|
adminAddr := conf.Get("admin", "addr")
|
||||||
|
|
||||||
serverAdmin := &http.Server{
|
serverAdmin := &http.Server{
|
||||||
Addr: adminbAddr,
|
Addr: adminAddr,
|
||||||
Handler: RunAdmin(),
|
Handler: RunAdmin(),
|
||||||
ReadTimeout: 5 * time.Second,
|
ReadTimeout: 5 * time.Second,
|
||||||
WriteTimeout: 10 * time.Second,
|
WriteTimeout: 10 * time.Second,
|
||||||
@ -142,7 +261,7 @@ func Help() {
|
|||||||
{K || __ _______ __
|
{K || __ _______ __
|
||||||
| PP / // / __(_)__ / /
|
| PP / // / __(_)__ / /
|
||||||
| || / _ / _// (_-</ _ \
|
| || / _ / _// (_-</ _ \
|
||||||
(__\\ /_//_/_/ /_/___/_//_/ v0.1
|
(__\\ /_//_/_/ /_/___/_//_/ v0.2
|
||||||
`
|
`
|
||||||
fmt.Println(color.Yellow(logo))
|
fmt.Println(color.Yellow(logo))
|
||||||
fmt.Println(color.White(" A Safe and Active Attack Honeypot Fishing Framework System for Enterprises."))
|
fmt.Println(color.White(" A Safe and Active Attack Honeypot Fishing Framework System for Enterprises."))
|
||||||
|
@ -6,6 +6,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"HFish/error"
|
"HFish/error"
|
||||||
"HFish/utils/conf"
|
"HFish/utils/conf"
|
||||||
|
"HFish/core/dbUtil"
|
||||||
|
"HFish/core/rpc/client"
|
||||||
|
"HFish/utils/is"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ReportWeb(c *gin.Context) {
|
func ReportWeb(c *gin.Context) {
|
||||||
@ -19,7 +22,44 @@ func ReportWeb(c *gin.Context) {
|
|||||||
if secKey != apiSecKey {
|
if secKey != apiSecKey {
|
||||||
c.JSON(http.StatusOK, error.ErrFailApiKey())
|
c.JSON(http.StatusOK, error.ErrFailApiKey())
|
||||||
} else {
|
} else {
|
||||||
report.ReportWeb(name, ip, info)
|
|
||||||
|
// 判断是否为 RPC 客户端
|
||||||
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("WEB", name, ip, info, "0")
|
||||||
|
} else {
|
||||||
|
go report.ReportWeb(name, "本机", ip, info)
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ReportDeepWeb(c *gin.Context) {
|
||||||
|
name := c.PostForm("name")
|
||||||
|
info := c.PostForm("info")
|
||||||
|
secKey := c.PostForm("sec_key")
|
||||||
|
ip := c.ClientIP()
|
||||||
|
|
||||||
|
apiSecKey := conf.Get("api", "sec_key")
|
||||||
|
|
||||||
|
if secKey != apiSecKey {
|
||||||
|
c.JSON(http.StatusOK, error.ErrFailApiKey())
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// 判断是否为 RPC 客户端
|
||||||
|
if is.Rpc() {
|
||||||
|
go client.ReportResult("DEEP", name, ip, info, "0")
|
||||||
|
} else {
|
||||||
|
go report.ReportDeepWeb(name, "本机", ip, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取记录黑客IP
|
||||||
|
func GetIpList(c *gin.Context) {
|
||||||
|
sql := `select ip from hfish_info GROUP BY ip;`
|
||||||
|
result := dbUtil.Query(sql)
|
||||||
|
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||||
|
}
|
||||||
|
38
view/colony/view.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package colony
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"net/http"
|
||||||
|
"HFish/core/dbUtil"
|
||||||
|
"HFish/error"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Html(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "colony.html", gin.H{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取蜜罐分类信息
|
||||||
|
func GetColony(c *gin.Context) {
|
||||||
|
sql := `
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
agent_name,
|
||||||
|
agent_ip,
|
||||||
|
web_status,
|
||||||
|
deep_status,
|
||||||
|
ssh_status,
|
||||||
|
redis_status,
|
||||||
|
mysql_status,
|
||||||
|
http_status,
|
||||||
|
telnet_status,
|
||||||
|
ftp_status,
|
||||||
|
last_update_time
|
||||||
|
FROM
|
||||||
|
hfish_colony
|
||||||
|
ORDER BY
|
||||||
|
id DESC;
|
||||||
|
`
|
||||||
|
|
||||||
|
result := dbUtil.Query(sql)
|
||||||
|
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||||
|
}
|
@ -15,16 +15,25 @@ func Html(c *gin.Context) {
|
|||||||
sqlSsh := `select count(1) as sum from hfish_info where type="SSH";`
|
sqlSsh := `select count(1) as sum from hfish_info where type="SSH";`
|
||||||
sqlRedis := `select count(1) as sum from hfish_info where type="REDIS";`
|
sqlRedis := `select count(1) as sum from hfish_info where type="REDIS";`
|
||||||
sqlMysql := `select count(1) as sum from hfish_info where type="MYSQL";`
|
sqlMysql := `select count(1) as sum from hfish_info where type="MYSQL";`
|
||||||
|
deepMysql := `select count(1) as sum from hfish_info where type="DEEP";`
|
||||||
|
telnetMysql := `select count(1) as sum from hfish_info where type="TELNET";`
|
||||||
|
ftpMysql := `select count(1) as sum from hfish_info where type="FTP";`
|
||||||
|
|
||||||
resultWeb := dbUtil.Query(sqlWeb)
|
resultWeb := dbUtil.Query(sqlWeb)
|
||||||
resultSsh := dbUtil.Query(sqlSsh)
|
resultSsh := dbUtil.Query(sqlSsh)
|
||||||
resultRedis := dbUtil.Query(sqlRedis)
|
resultRedis := dbUtil.Query(sqlRedis)
|
||||||
resultMysql := dbUtil.Query(sqlMysql)
|
resultMysql := dbUtil.Query(sqlMysql)
|
||||||
|
resultDeep := dbUtil.Query(deepMysql)
|
||||||
|
resultTelnet := dbUtil.Query(telnetMysql)
|
||||||
|
resultFtp := dbUtil.Query(ftpMysql)
|
||||||
|
|
||||||
webSum := strconv.FormatInt(resultWeb[0]["sum"].(int64), 10)
|
webSum := strconv.FormatInt(resultWeb[0]["sum"].(int64), 10)
|
||||||
sshSum := strconv.FormatInt(resultSsh[0]["sum"].(int64), 10)
|
sshSum := strconv.FormatInt(resultSsh[0]["sum"].(int64), 10)
|
||||||
redisSum := strconv.FormatInt(resultRedis[0]["sum"].(int64), 10)
|
redisSum := strconv.FormatInt(resultRedis[0]["sum"].(int64), 10)
|
||||||
mysqlSum := strconv.FormatInt(resultMysql[0]["sum"].(int64), 10)
|
mysqlSum := strconv.FormatInt(resultMysql[0]["sum"].(int64), 10)
|
||||||
|
deepSum := strconv.FormatInt(resultDeep[0]["sum"].(int64), 10)
|
||||||
|
telnetSum := strconv.FormatInt(resultTelnet[0]["sum"].(int64), 10)
|
||||||
|
ftpSum := strconv.FormatInt(resultFtp[0]["sum"].(int64), 10)
|
||||||
|
|
||||||
// 读取服务运行状态
|
// 读取服务运行状态
|
||||||
mysqlStatus := conf.Get("mysql", "status")
|
mysqlStatus := conf.Get("mysql", "status")
|
||||||
@ -32,17 +41,26 @@ func Html(c *gin.Context) {
|
|||||||
sshStatus := conf.Get("ssh", "status")
|
sshStatus := conf.Get("ssh", "status")
|
||||||
webStatus := conf.Get("web", "status")
|
webStatus := conf.Get("web", "status")
|
||||||
apiStatus := conf.Get("api", "status")
|
apiStatus := conf.Get("api", "status")
|
||||||
|
deepStatus := conf.Get("deep", "status")
|
||||||
|
telnetStatus := conf.Get("telnet", "status")
|
||||||
|
ftpStatus := conf.Get("ftp", "status")
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "dashboard.html", gin.H{
|
c.HTML(http.StatusOK, "dashboard.html", gin.H{
|
||||||
"webSum": webSum,
|
"webSum": webSum,
|
||||||
"sshSum": sshSum,
|
"sshSum": sshSum,
|
||||||
"redisSum": redisSum,
|
"redisSum": redisSum,
|
||||||
"mysqlSum": mysqlSum,
|
"mysqlSum": mysqlSum,
|
||||||
"webStatus": webStatus,
|
"deepSum": deepSum,
|
||||||
"sshStatus": sshStatus,
|
"telnetSum": telnetSum,
|
||||||
"redisStatus": redisStatus,
|
"ftpSum": ftpSum,
|
||||||
"mysqlStatus": mysqlStatus,
|
"webStatus": webStatus,
|
||||||
"apiStatus": apiStatus,
|
"sshStatus": sshStatus,
|
||||||
|
"redisStatus": redisStatus,
|
||||||
|
"mysqlStatus": mysqlStatus,
|
||||||
|
"apiStatus": apiStatus,
|
||||||
|
"deepStatus": deepStatus,
|
||||||
|
"telnetStatus": telnetStatus,
|
||||||
|
"ftpStatus": ftpStatus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +74,7 @@ func GetFishData(c *gin.Context) {
|
|||||||
FROM
|
FROM
|
||||||
hfish_info
|
hfish_info
|
||||||
WHERE
|
WHERE
|
||||||
strftime("%Y-%d", create_time) = strftime("%Y-%d", 'now')
|
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||||
AND type="WEB"
|
AND type="WEB"
|
||||||
GROUP BY
|
GROUP BY
|
||||||
hour;
|
hour;
|
||||||
@ -77,7 +95,7 @@ func GetFishData(c *gin.Context) {
|
|||||||
FROM
|
FROM
|
||||||
hfish_info
|
hfish_info
|
||||||
WHERE
|
WHERE
|
||||||
strftime("%Y-%d", create_time) = strftime("%Y-%d", 'now')
|
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||||
AND type="SSH"
|
AND type="SSH"
|
||||||
GROUP BY
|
GROUP BY
|
||||||
hour;
|
hour;
|
||||||
@ -98,7 +116,7 @@ func GetFishData(c *gin.Context) {
|
|||||||
FROM
|
FROM
|
||||||
hfish_info
|
hfish_info
|
||||||
WHERE
|
WHERE
|
||||||
strftime("%Y-%d", create_time) = strftime("%Y-%d", 'now')
|
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||||
AND type="REDIS"
|
AND type="REDIS"
|
||||||
GROUP BY
|
GROUP BY
|
||||||
hour;
|
hour;
|
||||||
@ -119,7 +137,7 @@ func GetFishData(c *gin.Context) {
|
|||||||
FROM
|
FROM
|
||||||
hfish_info
|
hfish_info
|
||||||
WHERE
|
WHERE
|
||||||
strftime("%Y-%d", create_time) = strftime("%Y-%d", 'now')
|
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||||
AND type="MYSQL"
|
AND type="MYSQL"
|
||||||
GROUP BY
|
GROUP BY
|
||||||
hour;
|
hour;
|
||||||
@ -132,12 +150,78 @@ func GetFishData(c *gin.Context) {
|
|||||||
mysqlMap[resultMysql[k]["hour"].(string)] = resultMysql[k]["sum"].(int64)
|
mysqlMap[resultMysql[k]["hour"].(string)] = resultMysql[k]["sum"].(int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 统计 deep
|
||||||
|
sqlDeep := `
|
||||||
|
SELECT
|
||||||
|
strftime("%H", create_time) AS hour,
|
||||||
|
sum(1) AS sum
|
||||||
|
FROM
|
||||||
|
hfish_info
|
||||||
|
WHERE
|
||||||
|
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||||
|
AND type="DEEP"
|
||||||
|
GROUP BY
|
||||||
|
hour;
|
||||||
|
`
|
||||||
|
|
||||||
|
resultDeep := dbUtil.Query(sqlDeep)
|
||||||
|
|
||||||
|
deepMap := make(map[string]int64)
|
||||||
|
for k := range resultDeep {
|
||||||
|
deepMap[resultDeep[k]["hour"].(string)] = resultDeep[k]["sum"].(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计 ftp
|
||||||
|
sqlFtp := `
|
||||||
|
SELECT
|
||||||
|
strftime("%H", create_time) AS hour,
|
||||||
|
sum(1) AS sum
|
||||||
|
FROM
|
||||||
|
hfish_info
|
||||||
|
WHERE
|
||||||
|
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||||
|
AND type="FTP"
|
||||||
|
GROUP BY
|
||||||
|
hour;
|
||||||
|
`
|
||||||
|
|
||||||
|
resultFtp := dbUtil.Query(sqlFtp)
|
||||||
|
|
||||||
|
ftpMap := make(map[string]int64)
|
||||||
|
for k := range resultFtp {
|
||||||
|
ftpMap[resultFtp[k]["hour"].(string)] = resultFtp[k]["sum"].(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计 Telnet
|
||||||
|
sqlTelnet := `
|
||||||
|
SELECT
|
||||||
|
strftime("%H", create_time) AS hour,
|
||||||
|
sum(1) AS sum
|
||||||
|
FROM
|
||||||
|
hfish_info
|
||||||
|
WHERE
|
||||||
|
strftime('%s', datetime('now')) - strftime('%s', create_time) < (24 * 3600)
|
||||||
|
AND type="TELNET"
|
||||||
|
GROUP BY
|
||||||
|
hour;
|
||||||
|
`
|
||||||
|
|
||||||
|
resultTelnet := dbUtil.Query(sqlTelnet)
|
||||||
|
|
||||||
|
telnetMap := make(map[string]int64)
|
||||||
|
for k := range resultTelnet {
|
||||||
|
telnetMap[resultTelnet[k]["hour"].(string)] = resultTelnet[k]["sum"].(int64)
|
||||||
|
}
|
||||||
|
|
||||||
// 拼接 json
|
// 拼接 json
|
||||||
s := map[string]map[string]int64{
|
s := map[string]map[string]int64{
|
||||||
"web": webMap,
|
"web": webMap,
|
||||||
"ssh": sshMap,
|
"ssh": sshMap,
|
||||||
"redis": redisMap,
|
"redis": redisMap,
|
||||||
"mysql": mysqlMap,
|
"mysql": mysqlMap,
|
||||||
|
"deep": deepMap,
|
||||||
|
"ftp": ftpMap,
|
||||||
|
"telnet": telnetMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, error.ErrSuccessEdit(s))
|
c.JSON(http.StatusOK, error.ErrSuccessEdit(s))
|
||||||
|
@ -5,21 +5,97 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"HFish/core/dbUtil"
|
"HFish/core/dbUtil"
|
||||||
"HFish/error"
|
"HFish/error"
|
||||||
|
"HFish/utils/page"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 钓鱼 页面
|
// 蜜罐 页面
|
||||||
func Html(c *gin.Context) {
|
func Html(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "fish.html", gin.H{})
|
c.HTML(http.StatusOK, "fish.html", gin.H{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取钓鱼列表
|
// 获取蜜罐列表
|
||||||
func GetFishList(c *gin.Context) {
|
func GetFishList(c *gin.Context) {
|
||||||
sql := `select id,type,project_name,ip,create_time from hfish_info ORDER BY id desc;`
|
p, _ := c.GetQuery("page")
|
||||||
result := dbUtil.Query(sql)
|
pageSize, _ := c.GetQuery("pageSize")
|
||||||
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
typex, _ := c.GetQuery("type")
|
||||||
|
soText, _ := c.GetQuery("so_text")
|
||||||
|
|
||||||
|
pInt, _ := strconv.ParseInt(p, 10, 64)
|
||||||
|
pageSizeInt, _ := strconv.ParseInt(pageSize, 10, 64)
|
||||||
|
|
||||||
|
pageStart := page.Start(pInt, pageSizeInt)
|
||||||
|
|
||||||
|
sql := `select id,type,project_name,agent,ip,ip_info,create_time from hfish_info where 1=1`
|
||||||
|
sqlx := `select count(1) as sum from hfish_info where 1=1`
|
||||||
|
sqlStatus := 0
|
||||||
|
|
||||||
|
if typex != "all" {
|
||||||
|
sql = sql + ` and type=?`
|
||||||
|
sqlx = sqlx + ` and type=?`
|
||||||
|
sqlStatus = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if soText != "" {
|
||||||
|
sql = sql + ` and (project_name like ? or ip like ?)`
|
||||||
|
sqlx = sqlx + ` and type=?`
|
||||||
|
if sqlStatus == 1 {
|
||||||
|
sqlStatus = 3
|
||||||
|
} else {
|
||||||
|
sqlStatus = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = sql + ` ORDER BY id desc LIMIT ?,?;`
|
||||||
|
|
||||||
|
if sqlStatus == 0 {
|
||||||
|
result := dbUtil.Query(sql, pageStart, pageSizeInt)
|
||||||
|
resultx := dbUtil.Query(sqlx)
|
||||||
|
pageCount := resultx[0]["sum"].(int64)
|
||||||
|
pageCount = page.TotalPage(pageCount, pageSizeInt)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": result,
|
||||||
|
"pageCount": pageCount,
|
||||||
|
"page": p,
|
||||||
|
})
|
||||||
|
} else if sqlStatus == 1 {
|
||||||
|
result := dbUtil.Query(sql, typex, pageStart, pageSizeInt)
|
||||||
|
resultx := dbUtil.Query(sqlx, typex)
|
||||||
|
pageCount := resultx[0]["sum"].(int64)
|
||||||
|
pageCount = page.TotalPage(pageCount, pageSizeInt)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": result,
|
||||||
|
"pageCount": pageCount,
|
||||||
|
"page": p,
|
||||||
|
})
|
||||||
|
} else if sqlStatus == 2 {
|
||||||
|
result := dbUtil.Query(sql, "%"+soText+"%", "%"+soText+"%", pageStart, pageSizeInt)
|
||||||
|
resultx := dbUtil.Query(sqlx, "%"+soText+"%", "%"+soText+"%")
|
||||||
|
pageCount := resultx[0]["sum"].(int64)
|
||||||
|
pageCount = page.TotalPage(pageCount, pageSizeInt)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": result,
|
||||||
|
"pageCount": pageCount,
|
||||||
|
"page": p,
|
||||||
|
})
|
||||||
|
} else if sqlStatus == 3 {
|
||||||
|
result := dbUtil.Query(sql, typex, "%"+soText+"%", "%"+soText+"%", pageStart, pageSizeInt)
|
||||||
|
resultx := dbUtil.Query(sqlx, typex, "%"+soText+"%", "%"+soText+"%")
|
||||||
|
pageCount := resultx[0]["sum"].(int64)
|
||||||
|
pageCount = page.TotalPage(pageCount, pageSizeInt)
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"data": result,
|
||||||
|
"pageCount": pageCount,
|
||||||
|
"page": p,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除钓鱼
|
// 删除蜜罐
|
||||||
func PostFishDel(c *gin.Context) {
|
func PostFishDel(c *gin.Context) {
|
||||||
id := c.PostForm("id")
|
id := c.PostForm("id")
|
||||||
sqlDel := `delete from hfish_info where id=?;`
|
sqlDel := `delete from hfish_info where id=?;`
|
||||||
@ -27,10 +103,17 @@ func PostFishDel(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取钓鱼信息
|
// 获取蜜罐信息
|
||||||
func GetFishInfo(c *gin.Context) {
|
func GetFishInfo(c *gin.Context) {
|
||||||
id, _ := c.GetQuery("id")
|
id, _ := c.GetQuery("id")
|
||||||
sql := `select info from hfish_info where id=?;`
|
sql := `select info from hfish_info where id=?;`
|
||||||
result := dbUtil.Query(sql, id)
|
result := dbUtil.Query(sql, id)
|
||||||
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取蜜罐分类信息
|
||||||
|
func GetFishTypeInfo(c *gin.Context) {
|
||||||
|
sql := `select type from hfish_info GROUP BY type;`
|
||||||
|
result := dbUtil.Query(sql)
|
||||||
|
c.JSON(http.StatusOK, error.ErrSuccess(result))
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"HFish/core/dbUtil"
|
"HFish/core/dbUtil"
|
||||||
"HFish/utils/send"
|
"HFish/utils/send"
|
||||||
"HFish/error"
|
"HFish/error"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Html(c *gin.Context) {
|
func Html(c *gin.Context) {
|
||||||
@ -30,6 +31,13 @@ func SendEmailToUsers(c *gin.Context) {
|
|||||||
config[2] = from
|
config[2] = from
|
||||||
}
|
}
|
||||||
|
|
||||||
send.SendMail(eArr, title, content, config)
|
status := strconv.FormatInt(isAlertStatus[0]["status"].(int64), 10)
|
||||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
|
||||||
|
if status == "1" {
|
||||||
|
send.SendMail(eArr, title, content, config)
|
||||||
|
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, error.ErrEmailFail())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -38,17 +38,12 @@ func checkInfo(id string) bool {
|
|||||||
if num == 2 && typeStr == "login" {
|
if num == 2 && typeStr == "login" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if num == 2 && typeStr == "alertOver" {
|
if num >= 4 && typeStr == "alertMail" {
|
||||||
return true
|
|
||||||
}
|
|
||||||
if num == 1 && typeStr == "pushBullet" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if num == 1 && typeStr == "fangTang" {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinInfo(args ...string) string {
|
func joinInfo(args ...string) string {
|
||||||
and := "&&"
|
and := "&&"
|
||||||
info := ""
|
info := ""
|
||||||
@ -74,10 +69,30 @@ func UpdateEmailInfo(c *gin.Context) {
|
|||||||
sql := `
|
sql := `
|
||||||
UPDATE hfish_setting
|
UPDATE hfish_setting
|
||||||
set info = ?,
|
set info = ?,
|
||||||
status = ?,
|
|
||||||
update_time = ?
|
update_time = ?
|
||||||
where id = ?;`
|
where id = ?;`
|
||||||
dbUtil.Update(sql, info, 0, time.Now().Format("2006-01-02 15:04"), id)
|
dbUtil.Update(sql, info, time.Now().Format("2006-01-02 15:04"), id)
|
||||||
|
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||||
|
}
|
||||||
|
|
||||||
|
/*更新警告邮件通知*/
|
||||||
|
func UpdateAlertMail(c *gin.Context) {
|
||||||
|
email := c.PostForm("email")
|
||||||
|
id := c.PostForm("id")
|
||||||
|
receive := c.PostForm("receive")
|
||||||
|
pass := c.PostForm("pass")
|
||||||
|
host := c.PostForm("host")
|
||||||
|
port := c.PostForm("port")
|
||||||
|
//subType := c.PostForm("type")
|
||||||
|
receiveArr := strings.Split(receive, ",")
|
||||||
|
receiveInfo := joinInfo(receiveArr...)
|
||||||
|
info := joinInfo(host, port, email, pass, receiveInfo)
|
||||||
|
sql := `
|
||||||
|
UPDATE hfish_setting
|
||||||
|
set info = ?,
|
||||||
|
update_time = ?
|
||||||
|
where id = ?;`
|
||||||
|
dbUtil.Update(sql, info, time.Now().Format("2006-01-02 15:04"), id)
|
||||||
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
c.JSON(http.StatusOK, error.ErrSuccessNull())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
92
view/url.go
@ -5,11 +5,13 @@ import (
|
|||||||
"HFish/view/dashboard"
|
"HFish/view/dashboard"
|
||||||
"HFish/view/fish"
|
"HFish/view/fish"
|
||||||
"HFish/view/mail"
|
"HFish/view/mail"
|
||||||
|
"HFish/view/colony"
|
||||||
"HFish/view/setting"
|
"HFish/view/setting"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"HFish/view/login"
|
"HFish/view/login"
|
||||||
"HFish/utils/conf"
|
"HFish/utils/conf"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"HFish/utils/is"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 解决跨域问题
|
// 解决跨域问题
|
||||||
@ -30,41 +32,73 @@ func cors() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LoadUrl(r *gin.Engine) {
|
func LoadUrl(r *gin.Engine) {
|
||||||
// 登录
|
// 判断是否为 RPC 客户端
|
||||||
r.GET("/login", login.Html)
|
if is.Rpc() {
|
||||||
r.POST("/login", login.Login)
|
/* RPC 客户端 */
|
||||||
r.GET("/logout", login.Logout)
|
|
||||||
|
|
||||||
// 仪表盘
|
// API 接口
|
||||||
r.GET("/", login.Jump, dashboard.Html)
|
// WEB 上报蜜罐信息
|
||||||
r.GET("/dashboard", login.Jump, dashboard.Html)
|
apiStatus := conf.Get("api", "status")
|
||||||
r.GET("/get/dashboard/data", login.Jump, dashboard.GetFishData)
|
|
||||||
|
|
||||||
// 钓鱼列表
|
// 判断 API 是否启用
|
||||||
r.GET("/fish", login.Jump, fish.Html)
|
if apiStatus == "1" {
|
||||||
r.GET("/get/fish/list", login.Jump, fish.GetFishList)
|
r.Use(cors())
|
||||||
r.GET("/get/fish/info", login.Jump, fish.GetFishInfo)
|
|
||||||
r.POST("/post/fish/del", login.Jump, fish.PostFishDel)
|
|
||||||
|
|
||||||
// 邮件群发
|
webUrl := conf.Get("api", "web_url")
|
||||||
r.GET("/mail", login.Jump, mail.Html)
|
deepUrl := conf.Get("api", "deep_url")
|
||||||
r.POST("/post/mail/sendEmail", login.Jump, mail.SendEmailToUsers)
|
|
||||||
|
|
||||||
// 设置
|
r.POST(webUrl, api.ReportWeb)
|
||||||
r.GET("/setting", login.Jump, setting.Html)
|
r.POST(deepUrl, api.ReportDeepWeb)
|
||||||
r.GET("/get/setting/info", login.Jump, setting.GetSettingInfo)
|
}
|
||||||
r.POST("/post/setting/update", login.Jump, setting.UpdateEmailInfo)
|
} else {
|
||||||
r.POST("/post/setting/checkSetting", login.Jump, setting.UpdateStatusSetting)
|
/* RPC 服务端 */
|
||||||
|
// 登录
|
||||||
|
r.GET("/login", login.Html)
|
||||||
|
r.POST("/login", login.Login)
|
||||||
|
r.GET("/logout", login.Logout)
|
||||||
|
|
||||||
// API 接口
|
// 仪表盘
|
||||||
// WEB 上报钓鱼信息
|
r.GET("/", login.Jump, dashboard.Html)
|
||||||
apiStatus := conf.Get("api", "status")
|
r.GET("/dashboard", login.Jump, dashboard.Html)
|
||||||
|
r.GET("/get/dashboard/data", login.Jump, dashboard.GetFishData)
|
||||||
|
|
||||||
// 判断 API 是否启用
|
// 蜜罐列表
|
||||||
if apiStatus == "1" {
|
r.GET("/fish", login.Jump, fish.Html)
|
||||||
r.Use(cors())
|
r.GET("/get/fish/list", login.Jump, fish.GetFishList)
|
||||||
|
r.GET("/get/fish/info", login.Jump, fish.GetFishInfo)
|
||||||
|
r.GET("/get/fish/typeList", login.Jump, fish.GetFishTypeInfo)
|
||||||
|
r.POST("/post/fish/del", login.Jump, fish.PostFishDel)
|
||||||
|
|
||||||
apiUrl := conf.Get("api", "url")
|
// 分布式集群
|
||||||
r.POST(apiUrl, api.ReportWeb)
|
r.GET("/colony", login.Jump, colony.Html)
|
||||||
|
r.GET("/get/colony/list", login.Jump, colony.GetColony)
|
||||||
|
|
||||||
|
// 邮件群发
|
||||||
|
r.GET("/mail", login.Jump, mail.Html)
|
||||||
|
r.POST("/post/mail/sendEmail", login.Jump, mail.SendEmailToUsers)
|
||||||
|
|
||||||
|
// 设置
|
||||||
|
r.GET("/setting", login.Jump, setting.Html)
|
||||||
|
r.GET("/get/setting/info", login.Jump, setting.GetSettingInfo)
|
||||||
|
r.POST("/post/setting/update", login.Jump, setting.UpdateEmailInfo)
|
||||||
|
r.POST("/post/setting/updateAlertMail", login.Jump, setting.UpdateAlertMail)
|
||||||
|
r.POST("/post/setting/checkSetting", login.Jump, setting.UpdateStatusSetting)
|
||||||
|
|
||||||
|
// API 接口
|
||||||
|
// WEB 上报蜜罐信息
|
||||||
|
apiStatus := conf.Get("api", "status")
|
||||||
|
|
||||||
|
// 判断 API 是否启用
|
||||||
|
if apiStatus == "1" {
|
||||||
|
r.Use(cors())
|
||||||
|
|
||||||
|
webUrl := conf.Get("api", "web_url")
|
||||||
|
deepUrl := conf.Get("api", "deep_url")
|
||||||
|
|
||||||
|
r.POST(webUrl, api.ReportWeb)
|
||||||
|
r.POST(deepUrl, api.ReportDeepWeb)
|
||||||
|
|
||||||
|
r.GET("/api/v1/get/ip", api.GetIpList)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
76
web/deep/html/index.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!--[if IE 8]>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" class="ie8" lang="zh-CN">
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if !(IE 8) ]><!-->
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
|
||||||
|
<!--<![endif]-->
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<title>登录 ‹ 内部管理平台 — WordPress</title>
|
||||||
|
<link rel='dns-prefetch' href='//s.w.org'/>
|
||||||
|
<link rel='stylesheet' id='dashicons-css' href='/static/dashicons.min.css?ver=5.2.2'
|
||||||
|
type='text/css' media='all'/>
|
||||||
|
<link rel='stylesheet' id='buttons-css' href='/static/buttons.min.css'
|
||||||
|
type='text/css' media='all'/>
|
||||||
|
<link rel='stylesheet' id='forms-css' href='/static/forms.min.css?ver=5.2.2' type='text/css'
|
||||||
|
media='all'/>
|
||||||
|
<link rel='stylesheet' id='l10n-css' href='/static/l10n.min.css?ver=5.2.2' type='text/css'
|
||||||
|
media='all'/>
|
||||||
|
<link rel='stylesheet' id='login-css' href='/static/login.min.css?ver=5.2.2' type='text/css'
|
||||||
|
media='all'/>
|
||||||
|
<meta name='robots' content='noindex,noarchive'/>
|
||||||
|
<meta name='referrer' content='strict-origin-when-cross-origin'/>
|
||||||
|
<meta name="viewport" content="width=device-width"/>
|
||||||
|
<style>
|
||||||
|
.login h1 a {
|
||||||
|
background-image: url(/static/w-logo-blue.png?ver=20131202);
|
||||||
|
background-image: none, url(/static/wordpress-logo.svg?ver=20131107);
|
||||||
|
background-size: 84px;
|
||||||
|
background-position: center top;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
color: #444;
|
||||||
|
height: 84px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.3em;
|
||||||
|
margin: 0 auto 25px;
|
||||||
|
padding: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
width: 84px;
|
||||||
|
text-indent: -9999px;
|
||||||
|
outline: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="login login-action-login wp-core-ui locale-zh-cn">
|
||||||
|
<div id="login">
|
||||||
|
<h1><a href="https://cn.wordpress.org/">基于WordPress</a></h1>
|
||||||
|
|
||||||
|
<form name="loginform" id="loginform" action="" method="post">
|
||||||
|
<p>
|
||||||
|
<label for="user_login">用户名或电子邮件地址<br/>
|
||||||
|
<input type="text" name="log" id="user_login" class="input" value="" size="20"
|
||||||
|
autocapitalize="off"/></label>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="user_pass">密码<br/>
|
||||||
|
<input type="password" name="pwd" id="user_pass" class="input" value="" size="20"/></label>
|
||||||
|
</p>
|
||||||
|
<p class="forgetmenot"><label for="rememberme"><input name="rememberme" type="checkbox" id="rememberme"
|
||||||
|
value="forever"/> 记住我的登录信息</label></p>
|
||||||
|
<p class="submit">
|
||||||
|
<input type="button" name="wp-submit" id="wp-submit" class="button button-primary button-large" onclick="report()" value="登录"/>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="/static/jquery.min.js"></script>
|
||||||
|
<script src="/static/x.js"></script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
web/deep/static/buttons.min.css
vendored
Normal file
2
web/deep/static/dashicons.min.css
vendored
Normal file
2
web/deep/static/forms.min.css
vendored
Normal file
5
web/deep/static/jquery.min.js
vendored
Normal file
2
web/deep/static/l10n.min.css
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/*! This file is auto-generated */
|
||||||
|
body.rtl,body.rtl .press-this a.wp-switch-editor{font-family:Tahoma,Arial,sans-serif}.rtl h1,.rtl h2,.rtl h3,.rtl h4,.rtl h5,.rtl h6{font-family:Arial,sans-serif;font-weight:600}body.locale-he-il,body.locale-he-il .press-this a.wp-switch-editor{font-family:Arial,sans-serif}.locale-he-il em{font-style:normal;font-weight:600}.locale-zh-cn #local-time,.locale-zh-cn #utc-time,.locale-zh-cn .form-wrap p,.locale-zh-cn .howto,.locale-zh-cn .inline-edit-row fieldset span.checkbox-title,.locale-zh-cn .inline-edit-row fieldset span.title,.locale-zh-cn .js .input-with-default-title,.locale-zh-cn .link-to-original,.locale-zh-cn .tablenav .displaying-num,.locale-zh-cn p.description,.locale-zh-cn p.help,.locale-zh-cn p.install-help,.locale-zh-cn span.description{font-style:normal}.locale-zh-cn .hdnle a{font-size:12px}.locale-zh-cn form.upgrade .hint{font-style:normal;font-size:100%}.locale-zh-cn #sort-buttons{font-size:1em!important}.locale-de-de #customize-header-actions .button,.locale-de-de-formal #customize-header-actions .button{padding:0 5px 1px}.locale-de-de #customize-header-actions .spinner,.locale-de-de-formal #customize-header-actions .spinner{margin:16px 3px 0}.locale-ru-ru #adminmenu{width:inherit}.locale-ru-ru #adminmenu,.locale-ru-ru #wpbody{margin-left:0}.locale-ru-ru .inline-edit-row fieldset label span.title,.locale-ru-ru .inline-edit-row fieldset.inline-edit-date legend{width:8em}.locale-ru-ru .inline-edit-row fieldset .timestamp-wrap,.locale-ru-ru .inline-edit-row fieldset label span.input-text-wrap{margin-left:8em}.locale-ru-ru.post-new-php .tagsdiv .newtag,.locale-ru-ru.post-php .tagsdiv .newtag{width:165px}.locale-ru-ru.press-this .posting{margin-right:277px}.locale-ru-ru .press-this-sidebar{width:265px}.locale-ru-ru #customize-header-actions .button{padding:0 5px 1px}.locale-ru-ru #customize-header-actions .spinner{margin:16px 3px 0}.locale-lt-lt .inline-edit-row fieldset label span.title,.locale-lt-lt .inline-edit-row fieldset.inline-edit-date legend{width:8em}.locale-lt-lt .inline-edit-row fieldset .timestamp-wrap,.locale-lt-lt .inline-edit-row fieldset label span.input-text-wrap{margin-left:8em}@media screen and (max-width:782px){.locale-lt-lt .inline-edit-row fieldset .timestamp-wrap,.locale-lt-lt .inline-edit-row fieldset label span.input-text-wrap,.locale-ru-ru .inline-edit-row fieldset .timestamp-wrap,.locale-ru-ru .inline-edit-row fieldset label span.input-text-wrap{margin-left:0}}
|
2
web/deep/static/login.min.css
vendored
Normal file
1
web/deep/static/wordpress-logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="64px" height="64px" viewBox="0 0 64 64" enable-background="new 0 0 64 64" xml:space="preserve"><style>.style0{fill: #0073aa;}</style><g><g><path d="M4.548 31.999c0 10.9 6.3 20.3 15.5 24.706L6.925 20.827C5.402 24.2 4.5 28 4.5 31.999z M50.531 30.614c0-3.394-1.219-5.742-2.264-7.57c-1.391-2.263-2.695-4.177-2.695-6.439c0-2.523 1.912-4.872 4.609-4.872 c0.121 0 0.2 0 0.4 0.022C45.653 7.3 39.1 4.5 32 4.548c-9.591 0-18.027 4.921-22.936 12.4 c0.645 0 1.3 0 1.8 0.033c2.871 0 7.316-0.349 7.316-0.349c1.479-0.086 1.7 2.1 0.2 2.3 c0 0-1.487 0.174-3.142 0.261l9.997 29.735l6.008-18.017l-4.276-11.718c-1.479-0.087-2.879-0.261-2.879-0.261 c-1.48-0.087-1.306-2.349 0.174-2.262c0 0 4.5 0.3 7.2 0.349c2.87 0 7.317-0.349 7.317-0.349 c1.479-0.086 1.7 2.1 0.2 2.262c0 0-1.489 0.174-3.142 0.261l9.92 29.508l2.739-9.148 C49.628 35.7 50.5 33 50.5 30.614z M32.481 34.4l-8.237 23.934c2.46 0.7 5.1 1.1 7.8 1.1 c3.197 0 6.262-0.552 9.116-1.556c-0.072-0.118-0.141-0.243-0.196-0.379L32.481 34.4z M56.088 18.8 c0.119 0.9 0.2 1.8 0.2 2.823c0 2.785-0.521 5.916-2.088 9.832l-8.385 24.242c8.161-4.758 13.65-13.6 13.65-23.728 C59.451 27.2 58.2 22.7 56.1 18.83z M32 0c-17.645 0-32 14.355-32 32C0 49.6 14.4 64 32 64s32-14.355 32-32.001 C64 14.4 49.6 0 32 0z M32 62.533c-16.835 0-30.533-13.698-30.533-30.534C1.467 15.2 15.2 1.5 32 1.5 s30.534 13.7 30.5 30.532C62.533 48.8 48.8 62.5 32 62.533z" class="style0"/></g></g></svg>
|
After Width: | Height: | Size: 1.5 KiB |
25
web/deep/static/x.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
function report() {
|
||||||
|
var login_field = $("#user_login").val();
|
||||||
|
var password = $("#user_pass").val();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "http://localhost:9001/api/v1/post/dart_report",
|
||||||
|
dataType: "json",
|
||||||
|
data: {
|
||||||
|
"name": "WordPress钓鱼",
|
||||||
|
"info": login_field + "&&" + password,
|
||||||
|
"sec_key": "9cbf8a4dcb8e30682b927f352d6559a0"
|
||||||
|
},
|
||||||
|
success: function (e) {
|
||||||
|
if (e.code == "200") {
|
||||||
|
alert("账号密码错误");
|
||||||
|
} else {
|
||||||
|
console.log(e.msg)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
console.log("fail")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -1,286 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<link rel="dns-prefetch" href="https://github.githubassets.com">
|
|
||||||
<link rel="dns-prefetch" href="https://avatars0.githubusercontent.com">
|
|
||||||
<link rel="dns-prefetch" href="https://avatars1.githubusercontent.com">
|
|
||||||
<link rel="dns-prefetch" href="https://avatars2.githubusercontent.com">
|
|
||||||
<link rel="dns-prefetch" href="https://avatars3.githubusercontent.com">
|
|
||||||
<link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
|
|
||||||
<link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
|
|
||||||
|
|
||||||
|
|
||||||
<link crossorigin="anonymous" media="all"
|
|
||||||
integrity="sha512-wOA18h3okAQWgNcdM+ifzeK5+sEAYkPVcS3CPFIdC9vvD4RGqtHlYG/K6x2baoGTWcyAj3WeE3ok4MpZk3Mtiw=="
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://github.githubassets.com/assets/frameworks-edbb2ca6a8a45d0c4f75a2f2e78a2d86.css"/>
|
|
||||||
<link crossorigin="anonymous" media="all"
|
|
||||||
integrity="sha512-vozupRTrzo7k2NwiAgC37o0fUpxqBtSeDrVyaxH6I+ed18h/6awTfjJ5uRMyNRb/2rOxZtsTi8FKLv7DVZMZwg=="
|
|
||||||
rel="stylesheet" href="https://github.githubassets.com/assets/site-c24aa206cdd4fb0b962ca6e303f5faca.css"/>
|
|
||||||
<link crossorigin="anonymous" media="all"
|
|
||||||
integrity="sha512-TtA9plNgcRtWEQtrabLyi5AfjZoT6h2cH0fHNbaLGq0hVhLeAbke8639lygQahMpdj4FHZ/P7HRjUrhM7SKY2Q=="
|
|
||||||
rel="stylesheet" href="https://github.githubassets.com/assets/github-bd06d35642be41f7a7b291a8c9923889.css"/>
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width">
|
|
||||||
|
|
||||||
<title>Sign in to GitHub · GitHub</title>
|
|
||||||
<meta name="description"
|
|
||||||
content="GitHub is where people build software. More than 36 million people use GitHub to discover, fork, and contribute to over 100 million projects.">
|
|
||||||
<link rel="search" type="application/opensearchdescription+xml" href="https://github.com/opensearch.xml"
|
|
||||||
title="GitHub">
|
|
||||||
<link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
|
|
||||||
<meta property="fb:app_id" content="1401488693436528">
|
|
||||||
|
|
||||||
<meta property="og:url" content="https://github.com">
|
|
||||||
<meta property="og:site_name" content="GitHub">
|
|
||||||
<meta property="og:title" content="Build software better, together">
|
|
||||||
<meta property="og:description"
|
|
||||||
content="GitHub is where people build software. More than 36 million people use GitHub to discover, fork, and contribute to over 100 million projects.">
|
|
||||||
<meta property="og:image" content="https://github.githubassets.com/images/modules/open_graph/github-logo.png">
|
|
||||||
<meta property="og:image:type" content="image/png">
|
|
||||||
<meta property="og:image:width" content="1200">
|
|
||||||
<meta property="og:image:height" content="1200">
|
|
||||||
<meta property="og:image" content="https://github.githubassets.com/images/modules/open_graph/github-mark.png">
|
|
||||||
<meta property="og:image:type" content="image/png">
|
|
||||||
<meta property="og:image:width" content="1200">
|
|
||||||
<meta property="og:image:height" content="620">
|
|
||||||
<meta property="og:image" content="https://github.githubassets.com/images/modules/open_graph/github-octocat.png">
|
|
||||||
<meta property="og:image:type" content="image/png">
|
|
||||||
<meta property="og:image:width" content="1200">
|
|
||||||
<meta property="og:image:height" content="620">
|
|
||||||
|
|
||||||
<meta property="twitter:site" content="github">
|
|
||||||
<meta property="twitter:site:id" content="13334762">
|
|
||||||
<meta property="twitter:creator" content="github">
|
|
||||||
<meta property="twitter:creator:id" content="13334762">
|
|
||||||
<meta property="twitter:card" content="summary_large_image">
|
|
||||||
<meta property="twitter:title" content="GitHub">
|
|
||||||
<meta property="twitter:description"
|
|
||||||
content="GitHub is where people build software. More than 36 million people use GitHub to discover, fork, and contribute to over 100 million projects.">
|
|
||||||
<meta property="twitter:image:src"
|
|
||||||
content="https://github.githubassets.com/images/modules/open_graph/github-logo.png">
|
|
||||||
<meta property="twitter:image:width" content="1200">
|
|
||||||
<meta property="twitter:image:height" content="1200">
|
|
||||||
|
|
||||||
<link rel="assets" href="https://github.githubassets.com/">
|
|
||||||
|
|
||||||
<meta name="pjax-timeout" content="1000">
|
|
||||||
|
|
||||||
<meta name="request-id" content="CD40:4908:95D966:D8940B:5D45BAD9" data-pjax-transient>
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="selected-link" value="/login" data-pjax-transient>
|
|
||||||
|
|
||||||
<meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
|
|
||||||
<meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
|
|
||||||
<meta name="google-site-verification" content="GXs5KoUUkNCoaAZn7wPN-t01Pywp9M3sEjnt_3_ZWPc">
|
|
||||||
|
|
||||||
<meta name="octolytics-host" content="collector.githubapp.com"/>
|
|
||||||
<meta name="octolytics-app-id" content="github"/>
|
|
||||||
<meta name="octolytics-event-url" content="https://collector.githubapp.com/github-external/browser_event"/>
|
|
||||||
<meta name="octolytics-dimension-request_id" content="CD40:4908:95D966:D8940B:5D45BAD9"/>
|
|
||||||
<meta name="octolytics-dimension-region_edge" content="ap-southeast-1"/>
|
|
||||||
<meta name="octolytics-dimension-region_render" content="iad"/>
|
|
||||||
<meta name="analytics-location-query-strip" content="true" data-pjax-transient="true"/>
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="google-analytics" content="UA-3769691-2">
|
|
||||||
|
|
||||||
|
|
||||||
<meta class="js-ga-set" name="dimension1" content="Logged Out">
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="hostname" content="github.com">
|
|
||||||
<meta name="user-login" content="">
|
|
||||||
|
|
||||||
<meta name="expected-hostname" content="github.com">
|
|
||||||
<meta name="js-proxy-site-detection-payload"
|
|
||||||
content="ODIyMGQ2MjA0NzE5ZWRkZDk0M2VlODRjYWU4NDU1MTIyMzczNThjNDA2ZDRlNTEwYzY1ZmY4MGQxYTA1NGQ1MHx7InJlbW90ZV9hZGRyZXNzIjoiMTIzLjExOS4xOTMuMTk4IiwicmVxdWVzdF9pZCI6IkNENDA6NDkwODo5NUQ5NjY6RDg5NDBCOjVENDVCQUQ5IiwidGltZXN0YW1wIjoxNTY0ODUwOTEzLCJob3N0IjoiZ2l0aHViLmNvbSJ9">
|
|
||||||
|
|
||||||
<meta name="enabled-features"
|
|
||||||
content="MARKETPLACE_FEATURED_BLOG_POSTS,MARKETPLACE_INVOICED_BILLING,MARKETPLACE_SOCIAL_PROOF_CUSTOMERS,MARKETPLACE_TRENDING_SOCIAL_PROOF,MARKETPLACE_RECOMMENDATIONS,MARKETPLACE_PENDING_INSTALLATIONS">
|
|
||||||
|
|
||||||
<meta name="html-safe-nonce" content="c8d4da83fc5d7c1f2ea0ba3fa441d25007f480ce">
|
|
||||||
|
|
||||||
<meta http-equiv="x-pjax-version" content="2cce593b0e01a05fd6f7a4a3f4f687e6">
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="canonical" href="https://github.com/login" data-pjax-transient>
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
|
|
||||||
|
|
||||||
<meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
|
|
||||||
|
|
||||||
<link rel="mask-icon" href="https://github.githubassets.com/pinned-octocat.svg" color="#000000">
|
|
||||||
<link rel="icon" type="image/x-icon" class="js-site-favicon" href="https://github.githubassets.com/favicon.ico">
|
|
||||||
|
|
||||||
<meta name="theme-color" content="#1e2327">
|
|
||||||
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="logged-out env-production emoji-size-boost page-responsive session-authentication">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="position-relative js-header-wrapper ">
|
|
||||||
<a href="#start-of-content" tabindex="1" class="px-2 py-4 bg-blue text-white show-on-focus js-skip-to-content">Skip
|
|
||||||
to content</a>
|
|
||||||
<div id="js-pjax-loader-bar" class="pjax-loader-bar">
|
|
||||||
<div class="progress"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="header header-logged-out width-full pt-5 pb-4" role="banner">
|
|
||||||
<div class="container clearfix width-full text-center">
|
|
||||||
<a class="header-logo" href="https://github.com/" aria-label="Homepage"
|
|
||||||
data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
|
|
||||||
<svg height="48" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="48"
|
|
||||||
aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="start-of-content" class="show-on-focus"></div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="application-main " data-commit-hovercards-enabled>
|
|
||||||
<main id="js-pjax-container" data-pjax-container>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="auth-form px-3" id="login">
|
|
||||||
|
|
||||||
<!-- '"` --><!-- </textarea></xmp> --></option></form>
|
|
||||||
<form autocomplete="off" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden"
|
|
||||||
value="✓"/><input type="hidden"
|
|
||||||
name="authenticity_token"
|
|
||||||
value="Xho925eUJBuTUnBHbjV5bkAtVefAgT+GTeecgA2jut69qSV7i9xjBdlfqQKbp/WIVmzt2Fsh2Q/iyol8l3FHrQ=="/>
|
|
||||||
<div class="auth-form-header p-0">
|
|
||||||
<h1>Sign in to GitHub</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="js-flash-container">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="auth-form-body mt-3">
|
|
||||||
|
|
||||||
<label for="login_field">
|
|
||||||
Username or email address
|
|
||||||
</label>
|
|
||||||
<input type="text" name="login" id="login_field" class="form-control input-block" tabindex="1"
|
|
||||||
autocapitalize="off" autocorrect="off" autofocus="autofocus"/>
|
|
||||||
|
|
||||||
<label for="password">
|
|
||||||
Password <a class="label-link" href="https://github.com/password_reset">Forgot password?</a>
|
|
||||||
</label>
|
|
||||||
<input type="password" name="password" id="password" class="form-control form-control input-block"
|
|
||||||
tabindex="2"/>
|
|
||||||
<input type="hidden" class="js-webauthn-support" name="webauthn-support" value="unknown">
|
|
||||||
|
|
||||||
<input onclick="report()" type="button" name="commit" value="Sign in" tabindex="3"
|
|
||||||
class="btn btn-primary btn-block"
|
|
||||||
data-disable-with="Signing in…"/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p class="create-account-callout mt-3">
|
|
||||||
New to GitHub?
|
|
||||||
<a data-hydro-click="{"event_type":"authentication.click","payload":{"location_in_page":"sign in switch to sign up","repository_id":null,"auth_type":"SIGN_UP","client_id":"1641767780.1540805591","originating_request_id":"CD40:4908:95D966:D8940B:5D45BAD9","originating_url":"https://github.com/login","referrer":null,"user_id":null}}"
|
|
||||||
data-hydro-click-hmac="73905671ce9d36942d12f2b8a36884ea43363a91abd92b7b37232721c77e81c4"
|
|
||||||
data-ga-click="Sign in, switch to sign up" href="https://github.com/join?source=login">Create an
|
|
||||||
account</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
<div class="modal-backdrop js-touch-events"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer container-lg p-responsive py-6 mt-6 f6" role="contentinfo">
|
|
||||||
<ul class="list-style-none d-flex flex-justify-center">
|
|
||||||
<li class="mr-3"><a href="https://github.com/site/terms"
|
|
||||||
data-ga-click="Footer, go to terms, text:terms">Terms</a></li>
|
|
||||||
<li class="mr-3"><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a>
|
|
||||||
</li>
|
|
||||||
<li class="mr-3"><a href="https://help.github.com/articles/github-security/"
|
|
||||||
data-ga-click="Footer, go to security, text:security">Security</a></li>
|
|
||||||
<li><a class="link-gray" data-ga-click="Footer, go to contact, text:contact" href="https://github.com/contact">Contact
|
|
||||||
GitHub</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="ajax-error-message" class="ajax-error-message flash flash-error">
|
|
||||||
<svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/>
|
|
||||||
</svg>
|
|
||||||
<button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
|
|
||||||
<svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
You can’t perform that action at this time.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script crossorigin="anonymous"
|
|
||||||
integrity="sha512-wnzuXnJOv44OjFcu212AzXLSnG/ZC2w+604e2QGofkYgeMWZYQfHPb6NeH6194cJ8HvT0UEsmHzlCSXPVHcg6w=="
|
|
||||||
type="application/javascript" src="https://github.githubassets.com/assets/frameworks-069938ed.js"></script>
|
|
||||||
|
|
||||||
<script crossorigin="anonymous" async="async"
|
|
||||||
integrity="sha512-/Y1wpl1aY/vedgylTJDuNAeobNLd980HlatSO7pdREx2e+kh4ijbkH0T8qp/+sJPD4FOBcodJ2AyDATrXVdVKA=="
|
|
||||||
type="application/javascript"
|
|
||||||
src="https://github.githubassets.com/assets/github-bootstrap-d62e19fe.js"></script>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner" hidden
|
|
||||||
>
|
|
||||||
<svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/>
|
|
||||||
</svg>
|
|
||||||
<span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
|
|
||||||
<span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
|
|
||||||
</div>
|
|
||||||
<template id="site-details-dialog">
|
|
||||||
<details class="details-reset details-overlay details-overlay-dark lh-default text-gray-dark hx_rsm" open>
|
|
||||||
<summary role="button" aria-label="Close dialog"></summary>
|
|
||||||
<details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
|
|
||||||
<button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button"
|
|
||||||
aria-label="Close dialog" data-close-dialog>
|
|
||||||
<svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16"
|
|
||||||
aria-hidden="true">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<div class="octocat-spinner my-6 js-details-dialog-spinner"></div>
|
|
||||||
</details-dialog>
|
|
||||||
</details>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;" tabindex="0">
|
|
||||||
<div class="Popover-message Popover-message--bottom-left Popover-message--large Box box-shadow-large"
|
|
||||||
style="width:360px;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div aria-live="polite" class="js-global-screen-reader-notice sr-only"></div>
|
|
||||||
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js" type="text/javascript"></script>
|
|
||||||
<script src="/static/github.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
76
web/wordPress/html/index.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<!--[if IE 8]>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" class="ie8" lang="zh-CN">
|
||||||
|
<![endif]-->
|
||||||
|
<!--[if !(IE 8) ]><!-->
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN">
|
||||||
|
<!--<![endif]-->
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
<title>登录 ‹ 内部管理平台 — WordPress</title>
|
||||||
|
<link rel='dns-prefetch' href='//s.w.org'/>
|
||||||
|
<link rel='stylesheet' id='dashicons-css' href='/static/dashicons.min.css?ver=5.2.2'
|
||||||
|
type='text/css' media='all'/>
|
||||||
|
<link rel='stylesheet' id='buttons-css' href='/static/buttons.min.css'
|
||||||
|
type='text/css' media='all'/>
|
||||||
|
<link rel='stylesheet' id='forms-css' href='/static/forms.min.css?ver=5.2.2' type='text/css'
|
||||||
|
media='all'/>
|
||||||
|
<link rel='stylesheet' id='l10n-css' href='/static/l10n.min.css?ver=5.2.2' type='text/css'
|
||||||
|
media='all'/>
|
||||||
|
<link rel='stylesheet' id='login-css' href='/static/login.min.css?ver=5.2.2' type='text/css'
|
||||||
|
media='all'/>
|
||||||
|
<meta name='robots' content='noindex,noarchive'/>
|
||||||
|
<meta name='referrer' content='strict-origin-when-cross-origin'/>
|
||||||
|
<meta name="viewport" content="width=device-width"/>
|
||||||
|
<style>
|
||||||
|
.login h1 a {
|
||||||
|
background-image: url(/static/w-logo-blue.png?ver=20131202);
|
||||||
|
background-image: none, url(/static/wordpress-logo.svg?ver=20131107);
|
||||||
|
background-size: 84px;
|
||||||
|
background-position: center top;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
color: #444;
|
||||||
|
height: 84px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.3em;
|
||||||
|
margin: 0 auto 25px;
|
||||||
|
padding: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
width: 84px;
|
||||||
|
text-indent: -9999px;
|
||||||
|
outline: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="login login-action-login wp-core-ui locale-zh-cn">
|
||||||
|
<div id="login">
|
||||||
|
<h1><a href="https://cn.wordpress.org/">基于WordPress</a></h1>
|
||||||
|
|
||||||
|
<form name="loginform" id="loginform" action="" method="post">
|
||||||
|
<p>
|
||||||
|
<label for="user_login">用户名或电子邮件地址<br/>
|
||||||
|
<input type="text" name="log" id="user_login" class="input" value="" size="20"
|
||||||
|
autocapitalize="off"/></label>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<label for="user_pass">密码<br/>
|
||||||
|
<input type="password" name="pwd" id="user_pass" class="input" value="" size="20"/></label>
|
||||||
|
</p>
|
||||||
|
<p class="forgetmenot"><label for="rememberme"><input name="rememberme" type="checkbox" id="rememberme"
|
||||||
|
value="forever"/> 记住我的登录信息</label></p>
|
||||||
|
<p class="submit">
|
||||||
|
<input type="button" name="wp-submit" id="wp-submit" class="button button-primary button-large" onclick="report()" value="登录"/>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="/static/jquery.min.js"></script>
|
||||||
|
<script src="/static/x.js"></script>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
web/wordPress/static/buttons.min.css
vendored
Normal file
2
web/wordPress/static/dashicons.min.css
vendored
Normal file
2
web/wordPress/static/forms.min.css
vendored
Normal file
5
web/wordPress/static/jquery.min.js
vendored
Normal file
2
web/wordPress/static/l10n.min.css
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/*! This file is auto-generated */
|
||||||
|
body.rtl,body.rtl .press-this a.wp-switch-editor{font-family:Tahoma,Arial,sans-serif}.rtl h1,.rtl h2,.rtl h3,.rtl h4,.rtl h5,.rtl h6{font-family:Arial,sans-serif;font-weight:600}body.locale-he-il,body.locale-he-il .press-this a.wp-switch-editor{font-family:Arial,sans-serif}.locale-he-il em{font-style:normal;font-weight:600}.locale-zh-cn #local-time,.locale-zh-cn #utc-time,.locale-zh-cn .form-wrap p,.locale-zh-cn .howto,.locale-zh-cn .inline-edit-row fieldset span.checkbox-title,.locale-zh-cn .inline-edit-row fieldset span.title,.locale-zh-cn .js .input-with-default-title,.locale-zh-cn .link-to-original,.locale-zh-cn .tablenav .displaying-num,.locale-zh-cn p.description,.locale-zh-cn p.help,.locale-zh-cn p.install-help,.locale-zh-cn span.description{font-style:normal}.locale-zh-cn .hdnle a{font-size:12px}.locale-zh-cn form.upgrade .hint{font-style:normal;font-size:100%}.locale-zh-cn #sort-buttons{font-size:1em!important}.locale-de-de #customize-header-actions .button,.locale-de-de-formal #customize-header-actions .button{padding:0 5px 1px}.locale-de-de #customize-header-actions .spinner,.locale-de-de-formal #customize-header-actions .spinner{margin:16px 3px 0}.locale-ru-ru #adminmenu{width:inherit}.locale-ru-ru #adminmenu,.locale-ru-ru #wpbody{margin-left:0}.locale-ru-ru .inline-edit-row fieldset label span.title,.locale-ru-ru .inline-edit-row fieldset.inline-edit-date legend{width:8em}.locale-ru-ru .inline-edit-row fieldset .timestamp-wrap,.locale-ru-ru .inline-edit-row fieldset label span.input-text-wrap{margin-left:8em}.locale-ru-ru.post-new-php .tagsdiv .newtag,.locale-ru-ru.post-php .tagsdiv .newtag{width:165px}.locale-ru-ru.press-this .posting{margin-right:277px}.locale-ru-ru .press-this-sidebar{width:265px}.locale-ru-ru #customize-header-actions .button{padding:0 5px 1px}.locale-ru-ru #customize-header-actions .spinner{margin:16px 3px 0}.locale-lt-lt .inline-edit-row fieldset label span.title,.locale-lt-lt .inline-edit-row fieldset.inline-edit-date legend{width:8em}.locale-lt-lt .inline-edit-row fieldset .timestamp-wrap,.locale-lt-lt .inline-edit-row fieldset label span.input-text-wrap{margin-left:8em}@media screen and (max-width:782px){.locale-lt-lt .inline-edit-row fieldset .timestamp-wrap,.locale-lt-lt .inline-edit-row fieldset label span.input-text-wrap,.locale-ru-ru .inline-edit-row fieldset .timestamp-wrap,.locale-ru-ru .inline-edit-row fieldset label span.input-text-wrap{margin-left:0}}
|