Blog转换至WordPress

2009-05-02

已从Blogengine完整转到wp程序
顺便把apple的也给转了.

日志与评论均已转换,分类就没有转了。

链接方面也没有转,准备手工加一下。

所有的日志链接已做301跳转,

ps,wp后台发布日志真叫慢啊。。

[BlogEngine扩展]IP2Area

2008-12-05

该插件将评论者的IP转为地区,数据取自于http://www.ip138.com
在这里表示感谢!
该扩展要在CommentAdded事件上进行处理,首先添加一个字段UserArea用来保存地区。
放心,这里的是异步处理的,不会造成页面的明显延迟。

代码如下:

/// <summary>
///Ip2Area 的摘要说明
/// </summary>
///
[Extension("IP转为地区", "1.0", "JasonYi")]
public class Ip2Area
{
public Ip2Area()
{
Post.CommentAdded += new EventHandler<EventArgs>(Post_CommentAdded);
}

void Post_CommentAdded(object sender, EventArgs e)
{
Comment p = sender as Comment;
if(p!=null)
new HttpAsyncGetIPArea(p,"http://www.ip138.com/ips.asp").Execute();
}
}
public class HttpAsyncGetIPArea
{
private Comment _comment;
private string _url;
public HttpAsyncGetIPArea(Comment c,string url)
{
_comment = c;
_url = url;
}
public Comment comment
{
get { return _comment; }
}
private HttpWebRequest Request;

public void Execute()
{
Request = (HttpWebRequest)WebRequest.Create(_url);
Request.Method = "POST";
Request.ContentType = "application/x-www-form-urlencoded";
Byte[] data = System.Text.Encoding.ASCII.GetBytes("action=2&ip=" + _comment.IP);
Request.ContentLength = data.Length;
using (Stream stream = Request.GetRequestStream())
{
stream.Write(data, 0, data.Length);
}
Request.BeginGetResponse(ProcessRespose, this);
}
private void ProcessRespose(IAsyncResult async)
{
HttpAsyncGetIPArea item = (HttpAsyncGetIPArea)async.AsyncState;
try
{
using (HttpWebResponse response = (HttpWebResponse)item.Request.EndGetResponse(async))
{
string RStr = string.Empty;

using (StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.Default))
{

RStr = sr.ReadToEnd();
}
Regex reg = new Regex(@"本站主数据:([^<li>]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
MatchCollection mc = reg.Matches(RStr);
string area = string.Empty;
if (mc.Count > 0)
{
area = mc[0].Value.Replace("本站主数据:", string.Empty);
}
if (!string.IsNullOrEmpty(area))
{
string sql = "UPDATE be_PostComment SET UserArea=@Area WHERE PostCommentID=@Id";
SqlParameter[] para = { new SqlParameter("@Area", area), new SqlParameter("@Id", comment.Id) };
SqlHelper.ExecuteNonQuery(SqlHelper.ConnectionString, System.Data.CommandType.Text, sql, para);
comment.UserArea = area;
}
}
}
catch (Exception ex)
{
//throw new System.Net.WebException("Get HttpResponse Error");
}
finally
{
Request.Abort();
}
}
}

[BlogEngine插件]对日志进行加密

2008-10-25

这里的对日志加密是指输入正确密码以后才能正常访问。
示例:
http://yibin.us/Archives/6035.aspx
密码:123456
针对于BlogEngine来说,可以用它丰富的插件机制来实现。

首先明确,完成这个插件需要做哪些工作:
1、数据表结构的变化。要增加密码项,“密码”字段当然是少不了了。
2、表结构的变化势必会引起Post实体类属性的变化。
3、Post.Serving时触发事件,该事件负责针对性的改变e.Body
4、认证的实现。
这里我用的是SqlServer数据库,打开be_Posts表,执行以下语句

ALTER TABLE dbo.be_Posts
ADD AccessPassword VARCHAR(100) NOT NULL DEFAULT ''
GO

对应地在BlogEngine.Core.Post中增加属性AccessPassword

private string _AccessPassword = string.Empty;
public string AccessPassword
{
get { return _AccessPassword; }
set {
if (_AccessPassword != value) MarkChanged(&quot;AccessPassword&quot;);
_AccessPassword = value;
}
}

这里的AccessPassword不为空表明该日志需要密码才能访问。当然,这里还得修改相应的数据访问层代码,来初始化AccessPassword字段。这里略去。
下一步编写插件,针对Post.Serving事件编写,全部代码:

[Extension(&quot;加密日志&quot;,&quot;1.0&quot;,&quot;yibin&quot;)]
public class ZPostAccessExtension : System.Web.SessionState.IRequiresSessionState
{
string cookiekey = string.Empty;
public ZPostAccessExtension()
{
Post.Serving += new EventHandler&lt;ServingEventArgs&gt;(Post_Serving);
}

void Post_Serving(object sender, ServingEventArgs e)
{
Post p = sender as Post;
if (HttpContext.Current.User.IsInRole(&quot;administrators&quot;) || HttpContext.Current.User.Identity.Name == p.Author)
return;
cookiekey = &quot;POSTACCESS_&quot; + p.Id;
//这里很关键,用Session来判断当前用户没有权限访问,如果指定的session存在,则直接退出,否则更改e.Body值。
if (HttpContext.Current.Session[cookiekey] != null)
{
return;
}
if (!string.IsNullOrEmpty(p.AccessPassword))
{
if (e.Location == ServingLocation.PostList)
{
e.Body = &quot;加密日志,您无权查看&quot;;
}
else
{
e.Body = string.Format(@&quot;&lt;p class=&quot;&quot;AccessPassword&quot;&quot;&gt;加密日志,请输入密码查阅&lt;/p&gt;&lt;input type=&quot;&quot;password&quot;&quot; name=&quot;&quot;AccessPassword_{0}&quot;&quot; id=&quot;&quot;AccessPassword_{0}&quot;&quot; class=&quot;&quot;AccessPassword&quot;&quot; /&gt;&lt;input type=&quot;&quot;button&quot;&quot; value=&quot;&quot;确定&quot;&quot; title=&quot;&quot;确定&quot;&quot; onclick=&quot;&quot;Check();&quot;&quot;/&gt;&quot;, p.PostIdentity);
e.Body = e.Body.Replace(&quot;&lt;&quot;, &quot;&lt;&quot;).Replace(&quot;&gt;&quot;, &quot;&gt;&quot;);  //这里要对已经HtmlEncode后的字符进行还原,否则显示不正确。
}
}
}
}

接下来就是实现认证这一步,我采用的是实现ICallbackEventHandler接口来进行无刷新操作。
先看很简单的js代码

&lt;script type=&quot;text/javascript&quot;&gt;
var accessMessage = document.getElementById('AccessMessage');
function Check()
{
accessMessage.style.display='block';
var pwd = '';
var panel = document.getElementById('AccessPassword_&lt;%=Post.PostIdentity %&gt;');  //DOM找到密码输入框
if(panel) {
pwd = panel.value;
}
if(pwd==''){
accessMessage.innerHTML='密码不能为空';
panel.focus();
return false;
}
accessMessage.innerHTML='正在验证......';
&lt;%= Page.ClientScript.GetCallbackEventReference(this,&quot;pwd&quot;,&quot;checkCallback&quot;,&quot;&quot;)%&gt;;
}

function checkCallback(rValue) {
if(rValue=='error')
{
accessMessage.style.display='block';
accessMessage.innerHTML='密码认证失败';
setTimeout(function(){accessMessage.style.display='none';},2000);
}
else{
accessMessage.style.display='block';
accessMessage.innerHTML='验证成功';
setTimeout(function(){document.getElementById('entrybody_&lt;%=Post.PostIdentity %&gt;').innerHTML = rValue;},500);
setTimeout(function(){accessMessage.style.display='none';},3000);
}
}
function ServerError(error) {
alert(error);
}
&lt;/script&gt;

.cs文件中的关于ICallbackEventHandler接口的实现

#region ICallbackEventHandler 成员
protected string _callback = string.Empty;
string ICallbackEventHandler.GetCallbackResult()
{
return _callback;
}

void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
if (Post.AccessPassword == eventArgument.Trim())
{
string SessionKey = &quot;POSTACCESS_&quot; + Post.Id;
HttpContext.Current.Session.Add(SessionKey, true);  //注意这里,当密码验证成功后写session
string path = Utils.RelativeWebRoot + &quot;themes/&quot; + BlogSettings.Instance.Theme + &quot;/PostView.ascx&quot;;

PostViewBase postView = (PostViewBase)LoadControl(path);
postView.Post = Post;
postView.Location = ServingLocation.SinglePost;
_callback = postView.Body;   //此处仍然会执行Post.Serving 事件,但因为前面已经写入了Session,所以触发Post.Serving 事件时将直接返回日志内容。
//因为我想每次访问日志都需要输密码,所以,当正确返回日志内容后,清除掉该Session
HttpContext.Current.Session.Remove(SessionKey);
}
else
{
_callback = &quot;error&quot;;
}
}

#endregion

OK,整个过程就是如此,很是简单吧。
同样的原理,可以实现类似于论坛的“回复后可见”同样的功能,如果想长时间维持日志的正常查看权,可将Session换成Cookie。
另外,BlogEngine默认在web.config中将enableSessionState设为了false,请一定要设为true,否则无法使用Session!
我就是没有认真看web.config,在Session的获取上大费周折。

密码保护:测试加密日志

2008-10-25

这是一篇受密码保护的文章。您需要提供访问密码:


浅谈BlogEngine扩展机制的实现

2008-07-29

以前写过一个针对于Blogengine的Mp3播放器扩展
在Blogengine里,每一个扩展的Class都必须有Extension标记
ExtensionAttribute是一个密封类,继承自System.Attribute,System.Attribute没有任何实现,只是一个标记而已。
ExtensionAttribute类的代码:

[AttributeUsage(AttributeTargets.Class)] //指明该Attrubite只能应用于Class上
public sealed class ExtensionAttribute : System.Attribute
{
/// <summary>
/// Creates an instance of the attribute and assigns a description.
/// </summary>
public ExtensionAttribute(string description, string version, string author)
{
_Description = description;
_Version = version;
_Author = author;
}

private string _Description;
/// <summary>
/// Gets the description of the extension.
/// </summary>
public string Description
{
get { return _Description; }
}

private string _Version;

/// <summary>
/// Gets the version number of the extension
/// </summary>
public string Version
{
get { return _Version; }
}

private string _Author;

/// <summary>
/// Gets the author of the extension
/// </summary>
public string Author
{
get { return _Author; }
}

}

这个类非常之简单,只是一些描述性的信息,如作者、版本、描述等。
到这里,ExtensionAttribute类就可以应用在任何一个Class上了
看看mp3player.cs

/// <summary>
/// 增加flashMp3播放器
/// </summary>
[Extension("mp3 player", "1.0.0.0", "lemongtree.com")] //此处给mp3player类打上Extension标记
public class mp3player
{
public mp3player()
{
Post.Serving += new EventHandler<ServingEventArgs>(Post_Serving);
}
private void Post_Serving(object sender, ServingEventArgs e)
{
//此处略过
}
}

mp3player就是一个针对于Post的扩展了。
有朋友可能会问了:mp3player这个在什么时候实例化的呢?或是我如何得知这个扩展是不是Enable的呢?
整个实例化的过程在global.asax的Application_Start事件中

Assembly a = Assembly.Load(assemblyName);
Type[] types = a.GetTypes(); //获取当前程序集中的所有类型

foreach (Type type in types)
{
object[] attributes = type.GetCustomAttributes(typeof(ExtensionAttribute), false); //获取含有ExtensionAttribute标记的类
foreach (object attribute in attributes)
{
if (ExtensionManager.ExtensionEnabled(type.Name))
{
a.CreateInstance(type.FullName); //创建实例
}
}
}

到这里很明了了,扩展类决定于它有没有打Extension标记,是否创建实例取决于该扩展有没有被启用.
BlogEngine有很多地方是值得我们学习的.

SqlMemberShip RoleProvider for blogengine

2008-07-28

自上次完成SqlDataProvider后
今天再次完成SqlMembershipProvider及RoleProvider
表结构
be_Users表

CREATE TABLE [dbo].[be_users](
[UserID] [int] IDENTITY(1000,1) NOT NULL,
[UserName] [nvarchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL CONSTRAINT [DF_be_users_UserName] DEFAULT (''),
[UserPassword] [varchar](100) COLLATE Chinese_PRC_CI_AS NOT NULL CONSTRAINT [DF_be_users_UserPassword] DEFAULT (''),
[UserEmail] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL CONSTRAINT [DF_be_users_UserEmail] DEFAULT (''),
CONSTRAINT [PK_be_users] PRIMARY KEY CLUSTERED
(
[UserID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

be_roles表

CREATE TABLE [dbo].[be_Roles](
[RoleID] [int] IDENTITY(1,1) NOT NULL,
[RoleName] [nvarchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL CONSTRAINT [DF_be_Roles_RoleName] DEFAULT (''),
CONSTRAINT [PK_be_Roles] PRIMARY KEY CLUSTERED
(
[RoleID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

be_UserRole表

CREATE TABLE [dbo].[be_UserRole](
[ID] [int] IDENTITY(1,1) NOT NULL,
[UserName] [nvarchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL,
[RoleName] [varchar](50) COLLATE Chinese_PRC_CI_AS NOT NULL,
CONSTRAINT [PK_be_UserRole] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

代码下载:
MSSqlProvider.rar (12.60 kb)
修改web.config

......
<membership defaultProvider="MsSqlMembershipProvider">
<providers>
<clear/>
<add name="XmlMembershipProvider" type="BlogEngine.Core.Providers.XmlMembershipProvider, BlogEngine.Core" description="XML membership provider" xmlFileName="~/App_Data/users.xml"/>
<add name="MsSqlMembershipProvider" type="BlogEngine.Core.Providers.MSSqlProvider.MsSqlMembershipProvider,BlogEngine.Core" description="Sql membership provider"/>
</providers>
</membership>
<roleManager defaultProvider="SqlRoleProvider" enabled="true" cacheRolesInCookie="true" cookieName=".BLOGENGINEROLES">
<providers>
<clear/>
<add name="XmlRoleProvider" type="BlogEngine.Core.Providers.XmlRoleProvider, BlogEngine.Core" description="XML role provider" xmlFileName="~/App_Data/roles.xml"/>
<add name="SqlRoleProvider" type="BlogEngine.Core.Providers.MSSqlProvider.SqlRoleProvider,BlogEngine.Core" description="" />
</providers>
</roleManager>
......

BlogEngine.NET js.axd bug

2008-04-15

在gmail的垃圾邮件里找到一封标题为《网站有漏洞哦》的邮件,好奇地打开邮件,内容是:

详细的参考:
http://blog.119797.com/post/BlogEngine-fix-js-Download-bug.aspx



by benben
:)

由于涉及到链接,不敢用ie,用firefox打开一看,是一篇名为BlogEngine.NET js.axd 模块漏洞及修复的文章,往下一看,果然在js.axd文件有致命的bug,该bug可以让攻击者拿到用户名及密码,有了这些,等于完全操纵了你的blog程序,可怕!

Bug分析:

js.axd文件是一个实现了IHttpHandler接口的处理文件,它的作用是将指定的js文件进行压缩与缓存,该文件需要传递一个path参数,如:js.axd?path=xxx.js,js.axd就会将xxx.js文件中的空白去掉以减小js文件的体积。

部分方法:

</p>
<p>private static string RetrieveLocalScript(string file)<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; string path = HttpContext.Current.Server.MapPath(file);<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; string script = null;<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; using (StreamReader reader = new StreamReader(path))<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; {<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; script = reader.ReadToEnd();<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; HttpContext.Current.Cache.Insert(file, script, new CacheDependency(path));<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br />
<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return script;<br />
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }</p>
<p>

但这里出现的bug就是前面所提到的,没有对传入的file文件格式做判断

访问js.axd?path=/web.config时,居然将我的web.config文件内容原样读取出来

继续访问js.axd?path=app_data/users.xml,结果同上,将这个xml文件的内容一行不漏地读取了,而该文件中存储的正是帐户信息!!

修补方法:

在private static string RetrieveLocalScript(string file)方法及private static string RetrieveRemoteScript(string file)方法中添加以下代码:

//判断是不是请求.js文件,如果不是则抛出安全性异常。

if (!file.EndsWith(".js", StringComparison.OrdinalIgnoreCase))
            {
                throw new System.Security.SecurityException("No access");
            }

再访问上述地址时:

 

 

问题基本得到修复,官方也就此bug发布了更新版本http://www.dotnetblogengine.net/post/Critical-Security-Patch-Available.aspx

使用dotnetblogengine的朋友一定要打此补丁,否则欲哭无泪了

在此,谢谢给我邮件的朋友,他的blog地址:

http://blog.119797.com/

[原创]BlogEngine.Net插件MP3播放器

2007-12-28

发现blogengine.net的插件制作还是比较简单的,因自己喜欢在blog里帖一些音乐文件
所以,参考其它插件的制作方法,制作了该插件,显示效果见这里:
[mp3:http://mrbo.net/mp3/kanon.mp3]

使用方法:
将附件中的mp3Player.cs文件放入blogengine.net\app_code\Extensions下
将mp3目录复制到blog根目录下,当然,你可以修改源代码来改变目录地址。
[mp3:文件地址]即可

mp3player.rar (9.10 kb)