之前同事发来一个邮箱匹配的正则表达式,^([a-z0-9A-Z]+[-|\\.|_]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$,说匹配的时候会卡住, 测试一下re.match("^([a-z0-9A-Z]+[-|\\.|_]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$", "sdfwerwerwerwerwsdfsdfsdfsdfsdfsdf"), 果然卡死 。找了很久没发现原因,后来看到浅析ReDoS的原理与实践后,才恍然大悟。([a-z0-9A-Z]+[-|\\.|_]?)+就是(a+)+这种形式, 而(a+)+这种类型的正则匹配时回溯次数特别多,很容易引起正则攻击.

类似的正则有

  • (a+)+
  • ([a-zA-Z]+)*
  • (a|aa)+
  • (a|a?)+
  • (.*a){x} for x > 10

查看《正则指引》,书中给了一个Python版本的正则表达式。r'^(?!\.)(?![\w.]*?\.\.)[\w.]{1,64}@(?=[-a-zA-Z0-9.]{0,255}(?![-a-zA-Z0-9.]))((?!-)[-a-zA-Z0-9]{1,63}\.)*(?!-)[-a-zA-Z0-9]{1,63}$'

其中@之前的部分是用户名,其长度不超过64个字符,所以是[\w.]{1,64}, 用户名不能以点号开头,所以添加环视(?!.), 同时用户名不能包含连续点号, 所以需要添加环视(?![\w.]*?\.\.)

@后面的部分是主机名,主机名分为很多段,每段长度不超过63,所以是[-a-zA-Z0-9]{1,63}, 且每段不能以横线开头,这一点用环视(?!-)保证,总长度不能超过255,用(?=[-a-zA-Z0-9.]{0,255}(?![-a-zA-Z0-9.]))来保证。主机名可以看做是”段+点号”的重复,所以是((?!-)[-a-zA-Z0-9]{1,63}\.)*, 最后的段必须出现,所以再添加一个(?!-)[-a-zA-Z0-9]{1,63}

看到晚上很多错误的例子,https://blog.csdn.net/xxm282828/article/details/43549959https://blog.csdn.net/bug_love/article/details/78799277https://www.oschina.net/code/snippet_1021818_47946,抄来抄去,都是错的。

联系作者

同事发了下面一个错误, 让我复现并找到解决的办法。错误原因是www.example.com的前端页面访问后端api.example.com出错。

1
2
3
4
5
{
"detail":"CSRF Failed: Referer checking failed -
https://www.example.com/ does not match any trusted origins."

}

CSRF validation does not work on Django using HTTPS中找到类似的错误,但是不知道怎么复现,在Issue with CsrfViewMiddleware and “referer” same_origin checking for secure (https) subdomains中发现有可能是CsrfViewMiddleware检测的原因。

于是才想起来可以用将”does not match any trusted origins”放到Django源码中搜索下,果然是在CsrfViewMiddleware里,于是将一些代码注释掉,知道在settings里配置一下CSRF_TRUSTED_ORIGINS即可,于是添加CSRF_TRUSTED_ORIGINS = [‘example.com’], 问题解决。

整个解决过程现在回想起来有些浪费时间,最有效的方法还是拿关键字到源码中找到对应代码,看看报错原因。

联系作者

实现文章列表时,写了如下组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const axios = require('axios');
var ComponentBlog = {
props: ['post'],
template: `
<div class="blog-post">
<h3>{{ post.title }}</h3>

<div v-html="post.content"></div>
</div>
`
}
export default {
components: {
'blog-post': ComponentBlog
},

但是希望组件能够在其它页面复用,于是放在一个文件中,然后导入进来。在这里,我放在BlogPost.vue中,文件内容如下

1
2
3
4
5
6
<template>
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
</template>

然后编写如下代码使用组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template>
<div>
<blog-post v-for="post in posts" :key="post.id" :post="post"></blog-post>
</div>
</template>

<script>
import BlogPost from '@/components/BlogPost'
const axios = require('axios');
export default {
components: {
BlogPost
},
data () {
return {
posts: [],
}
},
created() {
this.fetchPost();
},
methods: {
fetchPost() {
axios.get('http://127.0.0.1:7001/api/blog/posts/')
.then(response => {
console.log(response)
console.log(this)
this.posts = response.data.results
})
.catch(error => {
// handle error
console.log(error);
})
}
}
}
</script>

但是一直报post是未定义的。问过同事后,才知道在Vue文档中,有介绍单文件组件的使用方法。于是将BlogPost.vue改成如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="blog-post">
<h3>{{ post.title }}</h3>
<div v-html="post.content"></div>
</div>
</template>

<script type="text/javascript">

export default{
props: {
post: Object
},
data(){
return {
}
},
created(){
},
methods: {
},
}
</script>

这样之后,组件就可以正常使用了。

联系作者

在使用vue-router时,URL中总会有多了个#, 看着总是不爽,而且多了这个符号,以前被搜索引擎收录的页面就无法访问了,于是想办法去除。在Vue的文档中HTML5 History 模式找到了解答,也就是添加history模式即可。

1
2
3
4
const router = new VueRouter({
mode: 'history',
routes: [...]
})

看到警告中说,这么做以后,服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。

1
2
3
4
5
6
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
})

联系作者

在使用axios来进行Ajax请求时遇到这个问题。

使用如下代码向后端API请求时,无法对posts赋值,this是undefined

1
2
3
4
5
6
7
8
9
10
11
12
fetchPost() {
axios.get('http://127.0.0.1:7001/api/blog/posts/')
.then(function (response) {
console.log(response);
console.log(this);
this.posts = response.data.results
})
.catch(error => {
// handle error
console.log(error);
})
}

于是改成Vue教程中使用 axios 访问 API的箭头函数,这次可以得到结果。

1
2
3
4
5
6
7
8
9
10
11
12
fetchPost() {
axios.get('http://127.0.0.1:7001/api/blog/posts/')
.then(response => {
console.log(response);
console.log(this);
this.posts = response.data.results
})
.catch(error => {
// handle error
console.log(error);
})
}

深入浅出ES6(七):箭头函数 Arrow Functions里写到,普通function函数和箭头函数的行为有一个微妙的区别,箭头函数没有它自己的this值,箭头函数内的this值继承自外围作用域。原来如此。

联系作者

决定把自留地的前端代码独立开来,决定使用Vue来做。从0开始,遇到各种问题,打算记下来。最新遇到跨域问题,在访问服务的API时,报如下错误 No ‘Access-Control-Allow-Origin’ header is present on the requested resource,知道是跨域问题。看阮一峰跨域资源共享 CORS 详解,知道跨域是怎么回事。

以前就知道Dango有django-cors-headers模块用来支持跨域, 只是没有自己配置过。按照文档进行配置,加上白名单

1
2
3
CORS_ORIGIN_WHITELIST = (
'127.0.0.1:8081',
)

发现还是报相同的错误,改成

1
2
3
4
CORS_ORIGIN_WHITELIST = (
'127.0.0.1:8081',
'localhost:8081',
)

后就没有这个问题了。说明localhost和127.0.0.1是有区别的。

联系作者

在编写单元测试时,每次重新创建MySQL数据库表都花费很长时间,于是在表结果不变的情况下,想使用keepdb参数,即执行
python manage.py test --keepdb, 会报如下错误

1
2
Using existing test database for alias 'default'...
Got an error creating the test database: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CREATE DATABASE IF NOT EXISTS `test_tdcmdb` CHARACTER SET utf8mb4;\n ' at line 2")

查找错误来源,在django.db.backends.mysql.creation.py里的_execute_create_test_db函数中,

1
2
3
4
5
cursor.execute('''
SET @_tmp_sql_notes := @@sql_notes, sql_notes = 0;
CREATE DATABASE IF NOT EXISTS %(dbname)s %(suffix)s;
SET sql_notes = @_tmp_sql_notes;
''' % parameters)

改成

1
2
3
cursor.execute('''
CREATE DATABASE IF NOT EXISTS %(dbname)s %(suffix)s;
''' % parameters)

后,问题得到解决。

联系作者

最近公司开始使用Gerrit做code review工具,于是才知道git pull还有rebase这个参数。

当本地分支commit后,然后git pull拉取远程分支的提交,之后使用git review生成一个review时,会提示有多个提交。于是想到git rebase, 但是没想到的是git pull也有rebase参数。

洁癖者用 Git:pull –rebase 和 merge –no-ff里提到了pull –rebase的妙用,解决了多年的困惑。之前使用git pull时,老是生成很多无效的merge, 没想到使用git pull –rebase可以解决这个问题

联系作者

Celery定时任务里介绍,配置定时任务使用crontab, 并设置timezone。于是配置了timezone=”Asia/Shanghai”, 然而时间总是偏差8个小时,在Cannot use local time for cron schedules and UTC for solar schedules里找到解决的办法。原来问题的根源是Celery里的now函数定义出错了。查看now函数的实现

1
2
3
4
def now(self):
"""Return the current time and date as a datetime."""
from datetime import datetime
return datetime.utcnow().replace(tzinfo=self.timezone)

如果写成

1
2
3
4
def now(self):
"""Return the current time and date as a datetime."""
from datetime import datetime
return datetime.now(self.timezone)

就没问题了。
查看datetime.now的实现, 当tz不为空时, datetime.now(tz) 等于tz.fromutc(datetime.utcnow().replace(tzinfo=tz))。所以这是Celery的Bug

但是不能去修改Celery的源码,于是使用评论中提到的解决办法

1
2
3
4
5
6
7
class MyCelery(Celery)
def now(self):
"""Return the current time and date as a datetime."""
from datetime import datetime
return datetime.now(self.timezone)

app = MyCelery()

联系作者

自从买了1Password后,减轻了记密码的负担,整个人轻松了不少。

在浏览器上输密码时,可以使用Option + Command + \来弹出密码选项,之后搜索需要的密码,敲回车就可以登录。

但在item这种终端中,一直不知道如何输密码。看同事用了一次,才知道诀窍。在终端中,也是使用Option + Command + \来弹出密码选项,之后搜索需要的密码,然后用Shift + Command + C来复制密码,用Command + V来粘贴密码登录。

联系作者