Преглед изворни кода

【添加】WebNet 软件包

Signed-off-by: chenyong <1521761801@qq.com>
chenyong пре 7 година
родитељ
комит
7c6edd1286
51 измењених фајлова са 7912 додато и 2 уклоњено
  1. 52 0
      .gitignore
  2. 339 0
      LICENSE
  3. 104 2
      README.md
  4. 64 0
      SConscript
  5. 17 0
      docs/README.md
  6. 236 0
      docs/api.md
  7. BIN
      docs/figures/alias.jpg
  8. BIN
      docs/figures/alias_user.jpg
  9. BIN
      docs/figures/asp.jpg
  10. BIN
      docs/figures/auth.jpg
  11. BIN
      docs/figures/calc.jpg
  12. BIN
      docs/figures/gzip.jpg
  13. BIN
      docs/figures/hello_world.jpg
  14. BIN
      docs/figures/index.jpg
  15. BIN
      docs/figures/index_user.jpg
  16. BIN
      docs/figures/principle.jpg
  17. BIN
      docs/figures/root.jpg
  18. BIN
      docs/figures/ssi.jpg
  19. BIN
      docs/figures/upload_file.jpg
  20. BIN
      docs/figures/upload_file_show.jpg
  21. 102 0
      docs/introduction.md
  22. 26 0
      docs/principle.md
  23. 170 0
      docs/samples.md
  24. 521 0
      docs/user-guide.md
  25. 6 0
      docs/version.md
  26. 118 0
      inc/webnet.h
  27. 109 0
      inc/wn_module.h
  28. 135 0
      inc/wn_request.h
  29. 108 0
      inc/wn_session.h
  30. 47 0
      inc/wn_utils.h
  31. 98 0
      module/wn_module_alias.c
  32. 287 0
      module/wn_module_asp.c
  33. 136 0
      module/wn_module_auth.c
  34. 128 0
      module/wn_module_cgi.c
  35. 501 0
      module/wn_module_dav.c
  36. 119 0
      module/wn_module_index.c
  37. 112 0
      module/wn_module_log.c
  38. 181 0
      module/wn_module_lua.c
  39. 257 0
      module/wn_module_ssi.c
  40. 778 0
      module/wn_module_upload.c
  41. 81 0
      samples/index.html
  42. 14 0
      samples/index.shtml
  43. 10 0
      samples/version.asp
  44. 127 0
      samples/wn_sample.c
  45. 173 0
      samples/wn_sample_upload.c
  46. 202 0
      src/webnet.c
  47. 84 0
      src/wn_mimetype.c
  48. 561 0
      src/wn_module.c
  49. 950 0
      src/wn_request.c
  50. 619 0
      src/wn_session.c
  51. 340 0
      src/wn_utils.c

+ 52 - 0
.gitignore

@@ -0,0 +1,52 @@
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf

+ 339 - 0
LICENSE

@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 104 - 2
README.md

@@ -1,2 +1,104 @@
-# webnet
-A lightweight, customizable, embeddable Web Server for RT-Thread
+# WebNet
+
+## 1、介绍
+
+WebNet 软件包是自主研发的,基于 HTTP 协议的 Server 服务器实现,它不仅提供设备与 HTTP Client 通讯的基本功能,而且支持多种模块功能扩展,且资源占用少、可裁剪性强,充分满足开发者对嵌入式设备服务器的功能需求。
+
+WebNet 软件包功能特点如下:
+
+- 支持 HTTP 1.0/1.1
+- 支持 AUTH 基本认证功能
+- 支持 CGI 功能
+- 支持 ASP 变量替换功能
+- 支持 SSI 文件嵌入功能
+- 支持 INDEX 目录文件显示功能
+- 支持 ALIAS 别名访问功能
+- 支持文件上传功能
+- 支持预压缩功能
+- 支持缓存功能
+- 支持断点续传功能
+
+更多软件包功能特点介绍请查看 [详细介绍](docs/introduction.md)。 
+
+### 1.1 目录结构
+
+| 名称       | 说明                     |
+| ---------- | ------------------------ |
+| docs       | 文档目录                 |
+| inc        | 头文件目录               |
+| src        | 源文件目录               |
+| module     | 功能模块文件目录         |
+| samples    | 示例文件目录             |
+| LICENSE    | 许可证文件               |
+| README.md  | 软件包使用说明           |
+| SConscript | RT-Thread 默认的构建脚本 |
+
+### 1.2 许可证
+
+WebNet 软件包遵循 GPL2+ 商业双许可。该软件包可以根据 GUN 标准使用通用公共许可证,详见 LICENSE 文件。如果用于商业应用,可以通过电子邮箱 <business@rt-thread.com > 与我们联系获取商业许可。
+
+### 1.3 依赖
+
+- RT_Thread 3.0+
+- DFS 文件系统
+
+## 2、 获取软件包
+
+使用 WebNet软件包需要在 RT-Thread 的包管理中选中它,具体路径如下: 
+
+```shell
+RT-Thread online packages
+    IoT - internet of things  --->
+    	[*] WebNet: A HTTP Server for RT-Thread
+            (80)  Server listen port
+            (16)  Maximum number of server connections
+            (/webnet)   Server root directory
+                  Select supported modules  --->
+                     [ ] LOG: Enanle output log support
+                     [ ] AUTH: Enanle basic HTTP authentication support
+                     [ ] CGI: Enanle Common Gateway Interface support
+                     [ ] ASP: Enanle Active Server Pages support
+                     [ ] SSI: Enanle Server Side Includes support
+                     [ ] INDEX: Enanle list all the file in the directory support
+                     [ ] ALIAS: Enanle alias support
+                     [ ] DAV: Enanle Web-based Distributed Authoring and Versioning support
+                     [ ] UPLOAD: Enanle upload file support
+                     [ ] GZIP: Enable compressed file support by GZIP
+                     (0) CACHE: Configure cache level
+            [ ]   Enable webnet samples
+            	  Version (latest)  --->
+```
+
+**Server listen port**:配置服务器监听端口号;
+
+**Maximum number of server connections**:配置服务器最大连接数量;
+
+**Server root directory**:配置服务器根目录路径;
+
+**Select supported modules**:选择服务器支持的功能模块;
+
+**Enable webnet samples** :配置添加服务器示例文件;
+
+**Version**:配置软件包版本。
+
+配置完成后让 RT-Thread 的包管理器自动更新,或者使用 pkgs --update 命令更新包到 BSP 中。 
+
+## 3、使用 WebNet 软件包
+
+- 软件包详细介绍,请参考 [软件包介绍](docs/introduction.md)
+- 详细的示例介绍,请参考 [示例文档](docs/samples.md)
+- 如何从零开始使用,请参考 [用户指南](docs/user-guide.md)
+- 完整的 API 文档,请参考 [API 手册](docs/api.md)
+- 软件包工作原理,请参考 [工作原理](docs/principle.md)
+- 更多**详细介绍文档**位于 `/docs` 文件夹下,**使用软件包进行开发前请务必查看**。
+
+## 4、注意事项
+
+- WebNet 软件包使用需要文件系统支持,需要确保运行设备上能使用文件系统。
+- WebNet 软件包默认未开启任何模块功能支持,使用的需要根据[软件包介绍](docs/introduction.md)在 ENV 中开启需要的功能。
+
+## 5、联系方式 & 感谢
+
+- 维护:RT-Thread 开发团队
+- 主页:<https://github.com/RT-Thread-packages/webnet>
+

+ 64 - 0
SConscript

@@ -0,0 +1,64 @@
+#
+# File      : SConscript
+# This file is part of RT-Thread RTOS/WebNet Server
+# COPYRIGHT (C) 2011, Shanghai Real-Thread Technology Co., Ltd
+#
+# All rights reserved.
+#
+# Change Logs:
+# Date           Author       Notes
+# 2011-08-02     Bernard      the first version
+#
+
+Import('RTT_ROOT')
+from building import *
+
+cwd = GetCurrentDir()
+
+src = Split("""
+src/webnet.c
+src/wn_mimetype.c
+src/wn_request.c
+src/wn_session.c
+src/wn_utils.c
+src/wn_module.c
+""")
+
+if GetDepend(['WEBNET_USING_ASP']):
+    src += Glob('module/wn_module_asp.c')
+
+if GetDepend(['WEBNET_USING_AUTH']):
+    src += Glob('module/wn_module_auth.c')
+    
+if GetDepend(['WEBNET_USING_CGI']):
+    src += Glob('module/wn_module_cgi.c')
+
+if GetDepend(['WEBNET_USING_INDEX']):
+    src += Glob('module/wn_module_index.c')
+
+if GetDepend(['WEBNET_USING_ALIAS']):
+    src += Glob('module/wn_module_alias.c')
+    
+if GetDepend(['WEBNET_USING_LOG']):
+    src += Glob('module/wn_module_log.c')
+    
+if GetDepend(['WEBNET_USING_UPLOAD']):
+    src += Glob('module/wn_module_upload.c')
+    
+if GetDepend(['WEBNET_USING_SSI']):
+    src += Glob('module/wn_module_ssi.c')
+
+if GetDepend(['WEBNET_USING_DAV']):
+    src += Glob('module/wn_module_dav.c')
+    
+if GetDepend(['WEBNET_USING_SAMPLES']):
+    src += Glob('samples/wn_sample.c')
+
+if GetDepend(['WEBNET_USING_SAMPLES']) and GetDepend(['WEBNET_USING_UPLOAD']):
+    src += Glob('samples/wn_sample_upload.c')
+
+CPPPATH = [cwd + '/inc']
+
+group = DefineGroup('WebNet', src, depend = ['PKG_USING_WEBNET'], CPPPATH = CPPPATH)
+
+Return('group')

+ 17 - 0
docs/README.md

@@ -0,0 +1,17 @@
+# 文档
+
+## 软件包地址
+
+- https://github.com/RT-Thread-packages/webnet
+
+## 文档列表
+
+|文件名                             |描述|
+|:-----                             |:----|
+|[version.md](version.md)           |版本信息|
+|[introduction.md](introduction.md) |详细介绍|
+|[principle.md](principle.md)       |工作原理|
+|[user-guide.md](user-guide.md)     |使用指南|
+|[api.md](api.md)                   |API 说明|
+|[samples.md](samples.md)           |示例说明|
+

+ 236 - 0
docs/api.md

@@ -0,0 +1,236 @@
+# API 说明
+
+为了方便用户使用,这里列出了常用的 API,并给出了相关的使用说明。 
+
+## 初始化函数
+
+> int webnet_init(void);
+
+用于初始化 WebNet 服务器,包括创建线程用于监听客户端连接事件、初始化开启的功能模块等功能;
+
+| 参数     | 描述       |
+| :------- | :--------- |
+| 无       | 无         |
+| **返回** | **描述**   |
+| = 0      | 初始化成功 |
+| < 0      | 初始化失败 |
+
+## 设置监听套接字端口
+
+> void webnet_set_port(int port);
+
+用于设置当前 WebNet 服务器监听端口号,WebNet 服务器默认监听端口号是 80,这也是 HTTP 协议默认端口号。使用默认端口号访问 URL 地址时可以不输入端口号直接访问,当使用非默认端口号时,需要在 URL 地址上指明端口号,如:`http://host:8080/index.html` 。该函数只能**用于 WebNet 服务器初始化之前**。
+
+| 参数      | 描述                  |
+| :------- | :------------------- |
+| port     | 设置的监听套接字端口    |
+| **返回**  | **描述**              |
+| 无       | 无                   |
+
+## 获取监听套接字端口
+
+> int webnet_get_port(void);
+
+用于获取当前 WebNet 服务器监听套接字端口号。
+
+| 参数     | 描述             |
+| :------- | :--------------- |
+| 无       | 无               |
+| **返回** | **描述**         |
+| >=0      | 监听套接字端口号 |
+
+## 设置服务器根目录
+
+> void webnet_set_root(const char* webroot_path);
+
+用于设置当前 WebNet 服务器根目录路径,WebNet 服务器默认根目录为 `/webnet`,浏览器和 WebNet 函数中使用或访问的路径都是基于根目录路径。当浏览器访问 `http://host/index.html` 时,会把文件系统中的 `/webnet/index.html` 返回给浏览器。
+
+| 参数         | 描述             |
+| :----------- | :--------------- |
+| webroot_path | 设置的根目录地址 |
+| **返回**     | **描述**         |
+| 无           | 无               |
+
+## 获取服务器根目录
+
+> const char* webnet_get_root(void);
+
+用于获取当前 WebNet 服务器根目录地址。
+
+| 参数     | 描述       |
+| :------- | :--------- |
+| 无       | 无         |
+| **返回** | **描述**   |
+| != NULL  | 根目录地址 |
+
+## 获取请求链接的类型
+
+> const char* mime_get_type(const char* url);
+
+用于获取当前请求 URL 链接的类型,如:网页、图片、文本等。
+
+| 参数     | 描述           |
+| :------- | :------------- |
+| url      | 请求链接的地址 |
+| **返回** | **描述**       |
+| != NULL  | 请求链接的类型 |
+
+## 添加 ASP 变量处理方式
+
+> void webnet_asp_add_var(const char* name, void (*handler)(struct webnet_session* session));
+
+该函数用于添加一个 ASP 变量处理方式,当 ASP 文件中出现添加的 `name` 变量名时,会执行对应的 `handle`  操作。
+
+| 参数                                            | 描述             |
+| :---------------------------------------------- | :--------------- |
+| name                                            | ASP 变量名称     |
+| void (*handler)(struct webnet_session* session) | ASP 变量处理方式 |
+| **返回**                                        | **描述**         |
+| 无                                              | 无               |
+
+## 添加 CGI 事件处理方式
+
+> void webnet_cgi_register(const char* name, void (*handler)(struct webnet_session* session));
+
+该函数用于注册一个 CGI 事件处理方式,当浏览器请求带有 `name` 名称的 URL 时,会执行相应的 `handle` 操作。
+
+| 参数                                            | 描述             |
+| :---------------------------------------------- | :--------------- |
+| name                                            | CGI 事件名称     |
+| void (*handler)(struct webnet_session* session) | CGI 事件处理方式 |
+| **返回**                                        | **描述**         |
+| 无                                              | 无               |
+
+## 设置 CGI 事件根目录
+
+> void webnet_cgi_set_root(const char* root);
+
+WebNet 服务器默认的 CGI 事件根目录为`/cgi-bin`,当浏览器请求 `http://host/cgi-bin/test` 地址时,会执行 `test` 名称对应的 CGI 事件处理函数。
+
+该函数用于设置新的 CGI 事件根目录,设置成功之前的 CGI 根目录将不再起作用。
+
+| 参数     | 描述           |
+| :------- | :------------- |
+| root     | CGI 事件根目录 |
+| **返回** | **描述**       |
+| 无       | 无             |
+
+## 设置基本认证信息
+
+> void webnet_auth_set(const char* path, const char* username_password);
+
+用于设置目录访问时的基本认证信息,包括用户名和密码。
+
+| 参数              | 描述                                           |
+| :---------------- | :--------------------------------------------- |
+| path              | 需要设置基本认证信息的目录                     |
+| username_password | 设置的用户名和密码,格式为 `username:password` |
+| **返回**          | **描述**                                       |
+| 无                | 无                                             |
+
+## 设置目录别名
+
+> void webnet_alias_set(char* old_path, char* new_path);
+
+用于设置目录的别名,设置成功之后可以使用目录别名访问该目录。
+
+| 参数     | 描述                                     |
+| :------- | :--------------------------------------- |
+| old_path | 需要设置别名的目录                       |
+| new_path | 设置的目录别名,一般为服务器中存在的目录 |
+| **返回** | **描述**                                 |
+| 无       | 无                                       |
+
+## 发送 HTTP 请求头部
+
+> void webnet_session_set_header(struct webnet_session* session, const char* mimetype, int code, const char* title, int length);
+
+用于拼接并发送头部信息到连接的客户端,一般用于 ASP 变量处理函数和 CGI 事件处理函数中。
+
+| 参数     | 描述                                                         |
+| :------- | :----------------------------------------------------------- |
+| session  | 当前服务器连接的会话                                         |
+| mimetype | 需要发送的响应文件类型(Content-Type),可以使用  `mime_get_type`  函数获取 |
+| code     | 发送的响应状态码,正常为 200                                 |
+| title    | 发送的响应状态类型,正常为 OK                                |
+| length   | 需要发送的响应文件长度(Content-Length)                     |
+| **返回** | **描述**                                                     |
+| 无       | 无                                                           |
+
+## 发送 HTTP 响应数据
+
+> int  webnet_session_write(struct webnet_session* session, const rt_uint8_t* data, rt_size_t size);
+
+用于发送响应数据到客户端,一般用于 ASP 变量处理函数和 CGI 事件处理函数中。
+
+| 参数     | 描述                 |
+| :------- | :------------------- |
+| session  | 当前服务器连接的会话 |
+| data     | 发送的数据指针       |
+| size     | 发送的数据长度       |
+| **返回** | **描述**             |
+| 无       | 无                   |
+
+
+## 发送 HTTP 固定格式响应数据
+
+> void webnet_session_printf(struct webnet_session* session, const char* fmt, ...);
+
+用于发送固定格式的响应数据到客户端,一般用于 ASP 变量处理函数和 CGI 事件处理函数中。
+
+| 参数     | 描述                     |
+| :------- | :----------------------- |
+| session  | 当前服务器连接的会话     |
+| fmt      | 自定义的输入数据的表达式 |
+| ...      | 输入的参数               |
+| **返回** | **描述**                 |
+| 无       | 无                       |
+
+## 获取上传文件的名称
+
+> const char* webnet_upload_get_filename(struct webnet_session* session);
+
+获取当前上传文件的名称,用于打开或创建文件。
+
+| 参数     | 描述                 |
+| :------- | :------------------- |
+| session  | 当前服务器连接的会话 |
+| **返回** | **描述**             |
+| != NULL  | 当前上传文件的名称   |
+
+## 获取上传文件的类型
+
+> const char* webnet_upload_get_content_type(struct webnet_session* session);
+
+获取当前上传文件的类型。
+
+| 参数     | 描述                 |
+| :------- | :------------------- |
+| session  | 当前服务器连接的会话 |
+| **返回** | **描述**             |
+| != NULL  | 当前上传文件的类型   |
+
+## 获取上传文件参数
+
+>  const char* webnet_upload_get_nameentry(struct webnet_session* session, const char* name);
+
+获取注册的上传文件的分隔符(HTTP 请求 boundary 参数)。
+
+| 参数     | 描述                 |
+| :------- | :------------------- |
+| session  | 当前服务器连接的会话 |
+| name     | 上传文件的目录路径   |
+| **返回** | **描述**             |
+| != NULL  | 当前上传文件的类型   |
+
+## 获取上传文件打开的文件描述符
+
+> const void* webnet_upload_get_userdata(struct webnet_session* session);
+
+获取当前上传文件打开之后生成的文件描述符,用于读写数据到文件中。
+
+| 参数     | 描述                     |
+| :------- | :----------------------- |
+| session  | 当前服务器连接的会话     |
+| **返回** | **描述**                 |
+| != NULL  | 上传文件打开的文件描述符 |

BIN
docs/figures/alias.jpg


BIN
docs/figures/alias_user.jpg


BIN
docs/figures/asp.jpg


BIN
docs/figures/auth.jpg


BIN
docs/figures/calc.jpg


BIN
docs/figures/gzip.jpg


BIN
docs/figures/hello_world.jpg


BIN
docs/figures/index.jpg


BIN
docs/figures/index_user.jpg


BIN
docs/figures/principle.jpg


BIN
docs/figures/root.jpg


BIN
docs/figures/ssi.jpg


BIN
docs/figures/upload_file.jpg


BIN
docs/figures/upload_file_show.jpg


+ 102 - 0
docs/introduction.md

@@ -0,0 +1,102 @@
+# 软件包介绍
+
+WebNet 软件包是自主研发的,基于 HTTP 协议的 Server 服务器实现,它不仅提供设备与 HTTP Client 通讯的基本功能,而且支持多种模块功能扩展,满足开发者对嵌入式设备服务器的功能需求。
+
+## 软件包目录结构
+
+WebClient 软件包目录结构如下所示:
+
+| 名称       | 说明                     |
+| ---------- | ------------------------ |
+| docs       | 文档目录                 |
+| inc        | 头文件目录               |
+| src        | 源文件目录               |
+| module     | 功能模块文件目录         |
+| samples    | 示例文件目录             |
+| LICENSE    | 许可证文件               |
+| README.md  | 软件包使用说明           |
+| SConscript | RT-Thread 默认的构建脚本 |
+
+## 软件包功能特点
+
+WebNet 软件包提供丰富多样的功能支持,大部分功能以模块的形式给出,每个功能模块提供单独的控制方式且不同功能模块之间互不干扰,模块与主程序之间配合形成了一套功能齐全、高性能、高并发且高度可裁剪的网络服务器框架体系。
+
+WebNet 软件包功能特点:
+
+- **支持 HTTP 1.0/1.1**
+
+WebNet 软件包用于在嵌入式设备中运行网络服务器功能,其功能实现基于 HTTP 协议,且同时支持 HTTP 1.0 和 HTTP 1.1 协议,提高网络服务器兼容性。
+
+- **支持 CGI 功能**
+
+CGI(Common Gateway Interface )功能,是服务器运行时的外部程序规范,可以用于扩展服务器的功能。CGI 功能可以完成浏览器和服务器的交互过程。WebNet 软件包中的 CGI 功能,用户可以**自定义注册 CGI 执行函数**,服务器通过判断浏览器请求的 CGI 类型,执行相应的函数,并给予相应反馈信息。
+
+- **支持 ASP 变量替换功能**
+
+ASP(Active Server Pages)功能, 实现的网页中变量替换的功能,即当浏览器访问的页面代码中有 **<% %>** 标记出现时(**支持包含  .asp 后缀的页面文件**),将自动替换成**自定义注册的ASP 执行函数**,并且给与相应反馈信息。使用此功能可以很方便地独立修改每个变量执行函数的实现方式,完成整个页面不同部分的改动,方便前后台开发分工合作。
+
+- **支持 AUTH 基本认证功能**
+
+在使用 HTTP 协议进行通信的过程中,HTTP 协议定义了 Basic Authentication 基本认证过程以允许HTTP 服务器对 WEB 浏览器进行用户身份认证。基本认证功能为服务器提供简单的用户验证方式,其认证方式快速、准确,适用于对服务器安全性能有一定要求的系统和设备。WebNet 软件包中基本认证功能设计成与 URL 中目录相挂钩,在使用时可以**根据目录划分权限**。
+
+- **支持 INDEX 目录文件显示功能**
+
+WebNet 服务器开启之后,在浏览器输入对应的设备 IP 地址和目录名,就可以访问该目录,并列出该目录下所有文件的信息,类似于简单的文件服务器,可以用于服务器文件的查询和下载。
+
+- **支持 ALIAS 别名访问功能**
+
+ALIAS 别名访问功能,可以给文件夹设置多个别名,可以用于**长路径的简化操作**,方便用户对资源的访问。
+
+- **支持 SSI 文件嵌入功能**
+
+SSI(Server Side Include)功能,一般用于页面执行服务器程序或者插入文本内容到网页中。WebNet 中的 SSI 功能目前只支持插入文本内容到网页功能,页面代码中有 **<!--#include virtual="/xxx"-->** 或者 **<!--#include file="/xxx"-->** 标记存在时(**支持包含 .shtml 、shtm 后缀的页面文件**),将自动解析成对应文件中的信息。通过 SSI 文件嵌入功能,可以使整个页面处理模块化,从而实现修改指定文件就可以完成页面的改动。
+
+- **支持文件上传功能**
+
+支持浏览器上传文件到 WebNet 服务器,可以自定义文件处理函数,以实现把上传的文件存储在文件系统中或直接写入目标存储器。利用此功能可以实现:
+
+1. 上传资源文件到文件系统,如图片和网页;
+2. 上传存储器镜像以烧写系统中的存储器,如 SPI FLASH;
+3. 上传配置文件到系统以更新系统设置;
+4. 上传固件并直接写入 FLASH 以实现固件更新功能。
+
+- **支持预压缩功能**
+
+预压缩功能用于提前压缩网页资源文件(**支持包含 .gz 后缀的压缩文件**),降低对存储空间的需求,缩短文件系统读取时间,减少网络流量,提高网页加载速度。一般来讲,使用预压缩功能后,WebNet 服务器占用的存储空间仅为原来的 30%~40%。
+
+- **支持缓存功能**
+
+WebNet 缓存机制可以判断浏览器请求文件是否修改,从而决定是否发送完整文件内容到浏览器。 WebNet 软件包缓冲机制分为如下三个级别:
+
+1. level 0:关闭缓存功能,即浏览器每次都会从 WebNet 完整的读取文件内容;
+
+2. level 1:开启缓存功能,WebNet 通过读取请求文件的最后修改的时间,如果和本地文件相同,则返回 304  通知浏览器文件并无更新,不会发送文件;
+
+3. level 2:开启缓存功能,在原来判断修改时间的基础上,添加缓存文件有效时间支持,操作有效时间浏览器可重新访问该文件。
+
+- **支持断点续传功能**
+
+WebNet 服务器支持断点续传功能,即客户端在下载文件中途出现错误,再次下载时只需要提供给 WebNet 服务器前一次文件下载的偏移量,服务器将从指定文件偏移量发送文件内容给客户端,断点续传功能可以确保文件上传快速、准确,提高服务器运行效率。
+
+## 软件包性能测试
+
+测试环境:AM1808,主频 400M, DDR2 内存
+
+| 测试环境     | 页面文件在 SD 卡上,DM9000AEP 外置网卡 |
+| ------------ | -------------------------------------- |
+| 静态页面请求 | 142 pages/s                            |
+| CGI 页面请求 | 429 pages/s                            |
+
+| 测试环境     | 页面文件在 SD 卡上,EMAC 外置网卡 |
+| ------------ | --------------------------------- |
+| 静态页面请求 | 190 pages/s                       |
+| CGI 页面请求 | 567 pages/s                       |
+
+## 软件包资源占用
+
+ROM:16 Kbytes
+
+RAM:1.5 Kbytes + 1.1 Kbytes x 连接数
+
+NOTE:以上数据为参考值,示例平台是 Cortex-M3,因为指令长度和框架不同,软件包资源占用有所不同。WebNet 在处理页面时,文件系统和模块内部也会有部分资源消耗。
+

+ 26 - 0
docs/principle.md

@@ -0,0 +1,26 @@
+# 工作原理
+
+ WebNet 软件包主要用于在嵌入式设备上实现 HTTP 服务器,软件包的主要工作原理基于 HTTP 协议实现。
+
+![WebNet 软件包工作原理](figures/principle.jpg)
+
+HTTP 协议定义了客户端如何从服务器请求数据,以及服务器如何把数据传送给客户端的方式。HTTP 协议采用了`请求/响应模型`。 客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器通过解析请求头部信息,执行相应的功能模块,并且给客户端发送响应数据,响应数据的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。
+
+在 HTTP 协议的实际使用过程中,一般遵循以下流程:
+
+1. 客户端连接到服务器
+
+    通常是通过 TCP 三次握手建立 TCP 连接,WebNet 中默认的连接端口号为 80。
+
+2. 客户端发送 HTTP 请求
+
+    通过 TCP 套接字,客户端向 WebNet 发送一个请求报文,一个请求报文由请求行、请求头部、空行和请求数据四部分组成
+
+3. 服务器接收请求并解析数据信息
+
+    服务器接收到客户端的请求后,开启解析请求信息,定位服务器上请求的资源文件,然后向客户端发送响应数据,由客户端读取。一个响应数据由状态行、响应头部、空行和响应数据四部分组成。
+
+4. 客户端和服务器断开连接
+
+    若客户端和服务器之间连接模式为普通模式,则服务器主动关闭 TCP 连接,客户端被动关闭连接,释放 TCP 连接。若连接模式为 keepalive 模式,则该连接保持一段时间,在该时间内可以继续接收数据。
+

+ 170 - 0
docs/samples.md

@@ -0,0 +1,170 @@
+# 示例程序
+
+WebNet 软件包提供了一个综合的示例页面用于展示软件包的多项功能,包括:AUTH、CGI、ASP、SSI、INDEX、ALIAS、Upload 等功能。本章节主要介绍 WebNet 软件包中各个功能模块示例的使用方式。
+
+**示例文件**
+
+| 示例程序路径                         | 说明  |
+| ----                                | ---- |
+| samples/wn_sample.c      | 综合示例代码 |
+| samples/wn_sample_upload.c | 上传文件示例代码 |
+| samples/index.html | 综合示例页面 |
+| samples/index.shtml | SSI 功能示例页面 |
+| samples/version.asp | ASP 功能示例页面 |
+
+## 准备工作
+
+### 软件包获取
+
+- menuconfig 配置获取软件包和示例代码
+
+    打开 RT-Thread 提供的 ENV 工具,使用 **menuconfig** 配置软件包。
+
+    启用 WebNet 软件包,并配置使能测试例程配置(**Enable webnet samples**),如下所示:
+
+```shell
+RT-Thread online packages
+    IoT - internet of things  --->
+    	[*] WebNet: A HTTP Server for RT-Thread
+            (80)  Server listen port                   ## 服务器监听套接字端口号
+            (16)  Maximum number of server connections ## 服务器最大支持的连接数
+            (/webnet)   Server root directory          ## 服务器根目录
+                  Select supported modules  --->       ## 默认开启使用的功能模块
+                 	 [ ] LOG: Enanle output log support
+                  	 -*- AUTH: Enanle basic HTTP authentication support
+                  	 -*- CGI: Enanle Common Gateway Interface support
+                     -*- ASP: Enanle Active Server Pages support
+                     -*- SSI: Enanle Server Side Includes support
+                     -*- INDEX: Enanle list all the file in the directory support
+                     -*- ALIAS: Enanle alias support
+                     [ ] DAV: Enanle Web-based Distributed Authoring and Versioning support
+                     -*- UPLOAD: Enanle upload file support                  
+                     [ ] GZIP: Enable compressed file support by GZIP
+                     (0) CACHE: Configure cache level
+            [*]   Enable webnet samples  			    ## 开启测试例程
+            	  Version (latest)  --->
+```
+
+- 使用 `pkgs --update` 命令下载软件包
+
+- 编译下载
+
+### 页面文件准备
+
+WebNet 软件包示例中需要获取本地静态页面,需要文件系统的支持(FAT 文件系统,ROMFS 文件系统等,只需要支持 RT-Thread 的设备虚拟文件系统)。
+
+静态页面需要上传到文件系统中服务器根目录下(示例中使用根目录为 /webnet)。设备挂载文件系统成功,需要依次执行下面操作:
+
+1. 使用 `mkdir webnet` 命令创建 WebNet 软件包根目录 **/webnet**,并使用 `cd webnet` 命令进入该目录;
+
+2. 使用 `mkdir admin` 和 `mkdir upload` 命令创建 **/webnet/admin** 和 **/webnet/upload** ,用于 AUTH 功能和 Upload 功能测试;
+
+3. 将 WebNet 软件包 /sample 目录下的:**index.html**、**index.shtml**、**version.asp** 三个文件依次上传到设备 **/webnet** 目录(WebNet 根目录)中。(可以使用 TFTP 工具上传文件,具体操作方式参考 [TFTP 使用说明](https://github.com/RT-Thread-packages/netutils/blob/master/tftp/README.md))
+
+创建目录和上传文件成功之后,就可以启动例程,测试 WebNet 软件功能。
+
+## 启动例程 
+
+本例程参数和环境配置如下:
+
+- 监听端口号:80
+
+- 根目录地址:/webnet
+
+- 文件系统:FAT 文件系统
+
+设备启动,连接网络成功之后,在 Shell 命令行输入 `webnet_test` 命令启动 WebNet 服务器。查看 Shell 命令行,显示如下日志信息,说明 WebNet 服务器初始化成功:
+
+```shell
+msh />webnet_test
+[I/wn] RT-Thread webnet package (V2.0.0) initialize success.
+```
+
+然后在 Shell 命令行中使用 `ifconfig` 命令获取本设备 IP地址为 **192.168.12.29**。
+
+```shell
+msh />ifconfig
+network interface: w0 (Default)
+MTU: 1500
+MAC: 44 32 c4 75 e0 59 
+FLAGS: UP LINK_UP ETHARP BROADCAST IGMP
+ip address: 192.168.12.29
+gw address: 192.168.10.1
+net mask  : 255.255.0.0
+dns server #0: 192.168.10.1
+dns server #1: 223.5.5.5
+```
+
+接着在浏览器(这里使用谷歌浏览器)中输入设备 IP 地址,将默认访问设备根目录下 **/index.html** 文件,如下图所示,页面文件正常显示:
+
+![例程主页面](figures/root.jpg)
+
+该页面上显示了 WebNet 软件包的基本功能介绍,并根据不同的功能给出相应的演示示例,下面将按顺序介绍如下几个例程:
+
+- AUTH 基本认证例程
+- CGI 事件处理例程
+- ASP 变量替换例程
+- SSI 文件嵌套例程
+- INDEX 目录显示例程
+- ALIAS 别名访问例程
+- Upload 文件上传例程
+
+###  AUTH 基本认证例程
+
+在例程主页( /index.html)AUTH Test   模块下点击 `基本认证功能测试:用户名及密码为 admin:admin` 按键,弹出基本认证对话框,输入用户名 **admin**,密码 **admin**。成功输入用户名和密码后,会进入根目录下 /admin 目录,如下图所示流程:
+
+![基本认证例程](figures/auth.jpg)
+
+### CGI 事件处理例程
+
+本例程提供两个 CGI 示例:**hello world 例程** 和 **calc 例程** ,用于演示 CGI 事件处理的基本功能。
+
+- **hello world 例程**
+
+hello world 例程演示了在页面演示文本内容功能,在例程主页 CGI Test  模块下点击 `> hello world` 按键,会跳转到新页面显示日志信息,新页面显示了 hello world 说明 CGI 事件处理成功,然后在新页面点击 `Go back to root`  按键回到例程主页面,如下图所示:
+
+![hello world 例程](figures/hello_world.jpg)
+
+- **calc 例程**
+
+calc 例程中使用 CGI 功能在页面上展示了简单的加法计算器功能,在例程主页 CGI Test  模块下点击 `> calc`  按键,会跳转到新的页面,输入两个数字点击 `计算` 按键,页面会显示两数字相加的结果。在新页面点击 `Go back to root`  按键可以回到例程主页面,如下图所示:
+
+![calc 例程](figures/calc.jpg)
+
+### ASP 变量替换例程
+
+ASP 例程演示了页面变量替换的功能。在例程主页 ASP Test  模块下点击 `ASP 功能测试:访问 version.asp 文件` 按键,会跳转到根目录下 version.asp 页面,该页面显示当前使用 RT-Thread 系统的版本号。在新页面点击 `Go back to root`  按键回到例程主页面,如下图所示:
+
+![ASP 例程](figures/asp.jpg)
+
+### SSI 文件嵌套例程
+
+SSI 例程演示在一个页面中嵌套另一个页面的功能。在例程主页 SSI Test  模块下点击 `SSI 功能测试:访问 /version.shtml 页面` 按键,会跳转到根目录下 index.shtml 页面,该页面中嵌套显示了主页面 index.html 内容。在新页面点击 `Go back to root`  按键回到例程主页面。
+
+![SSI 例程](figures/asp.jpg)
+
+### INDEX 目录显示例程
+
+INDEX 例程演示页面文件列表功能。
+
+首先需要任意上传一个文件到根目录的 /admin 目录中,例程中已上传了 admin.txt 文件到该目录中。
+
+然后在例程主页 INDEX Test 模块下点击 `INDEX 功能测试:访问/admin 目录` 按键,会跳转到根目录下 /admin 目录,并且列出该目录下所有文件名和文件长度,如下图所示:
+
+![INDEX 例程](figures/index.jpg)
+
+### ALIAS 别名访问例程
+
+ALIAS 例程演示了使用目录别名访问该目录的功能。该例程代码中已经将 /test 目录设置别名为 /admin, 在例程主页 ALIAS Test 模块下点击 `ALIAS 功能测试:访问 /test 目录会跳转到 /admin 目录 ` 按键,实际我们访问的是 /test  目录,但会跳转访问 /admin 目录,并列出该目录下所有文件信息,如下图所示:
+
+![ALIAS 例程](figures/alias.jpg)
+
+### Upload 文件上传例程
+
+Upload 例程实现上传文件到 WebNet 服务器固定目录功能。在例程主页上 Upload File Test 模块下点击 `选择文件` 按键,选取需要上传的文件(本例程是用 upload.txt 文件),点击 `上传`,可以将文件上传到根目录下的 /upload 目录,如下图所示:
+
+![上传文件](figures/upload_file.jpg)
+
+文件上传成功之后,返回例程主页,点击 `浏览上传文件的目录` 按键,可以访问 /upload 文件,查看刚才上传的文件信息。
+
+![查看上传文件](figures/upload_file_show.jpg)

+ 521 - 0
docs/user-guide.md

@@ -0,0 +1,521 @@
+# 使用指南
+
+本节主要介绍 WebNet 软包的基本使用流程, 并针对使用过程中经常涉及到的结构体和重要 API 进行简要说明。
+
+## 准备工作
+
+### ENV 配置说明
+
+首先需要下载 WebNet 软件包,并将软件包加入到项目中。在 BSP 目录下使用 menuconfig 命令打开 ENV 配置界面,在 `RT-Thread online packages → IoT - internet of things` 中选择 WebNet软件包,具体路径如下:
+
+```shell
+RT-Thread online packages
+    IoT - internet of things  --->
+    	[*] WebNet: A HTTP Server for RT-Thread
+            (80)  Server listen port
+            (16)  Maximum number of server connections
+            (/webnet)   Server root directory
+                  Select supported modules  --->
+                     [*] LOG: Enanle output log support
+                     [*] AUTH: Enanle basic HTTP authentication support
+                     [*] CGI: Enanle Common Gateway Interface support
+                     [*] ASP: Enanle Active Server Pages support
+                     [*] SSI: Enanle Server Side Includes support
+                     [*] INDEX: Enanle list all the file in the directory support
+                     [*] ALIAS: Enanle alias support  
+                     [*] DAV: Enanle Web-based Distributed Authoring and Versioning support
+                     [*] UPLOAD: Enanle upload file support
+                     [*] GZIP: Enable compressed file support by GZIP
+                     (2) CACHE: Configure cache level
+                     	(1800) Cache-Control time in seconds   
+            [*]   Enable webnet samples
+            	  Version (latest)  --->
+```
+
+**Server listen port**:配置服务器监听端口号;
+
+**Maximum number of server connections**:配置服务器最大支持连接数量;
+
+**Server root directory**:配置服务器根目录路径;
+
+**Select supported modules**:选择服务器支持的功能模块,默认开启全部功能模块支持;
+
+- **LOG**:配置开启 WebNet 软件包日志功能支持;
+- **AUTH**:配置开启 Basic Authentication 基本认证功能支持;
+- **CGI**:配置开启 CGI 事件处理功能支持;
+- **ASP**:配置开启 ASP 变量替换功能支持;
+- **SSI**:配置开启文件嵌入功能支持;
+- **INDEX**:配置开启显示当前页面文件列表功能支持;
+- **ALIAS**:配置开启别名访问功能支持;
+- **DAV**:配置开启基于 Web 的分布式创作和版本控制支持; 
+- **Upload**:配置开启文件上传功能支持;
+- **GZIP**:配置开启服务器压缩文件访问支持;
+- **CHCHE**:配置开启缓存功能支持(可选级别 0,1,2);
+- **Cache-Control time**:配置缓存功能有效时间,需要缓存功能等级为 2;
+
+**Enable webnet samples** :配置添加服务器示例文件;
+
+**Version**:配置软件包版本。
+
+这里我们默认开启 WebNet 软件包**全部功能支持**,版本号选择 **latest** 最新版本。选择合适的配置项和版本后,使用 `pkgs --update` 命令下载软件包并更新用户配置。
+
+## 文件系统使用说明
+
+WebNet 软件包使用,需要文件系统的支持(FAT 文件系统,ROMFS 文件系统等,支持 RT-Thread 的设备虚拟文件系统),用于 WebNet 软件包中访问的静态页面的存储、上传下载文件的存储等功能。
+
+## 工作流程
+
+使用 WebNet 软件包之前,可以先了解 WebNet 软件包基本工作流程,如下所示:
+
+- 初始化 WebNet 软件包,启动监听连接请求;
+
+- 接收连接请求,创建连接会话;
+
+- 接收 HTTP 请求数据,解析请求信息;
+
+- 判断请求的功能模块,执行对应的功能;
+
+- 返回 HTTP 响应数据 ;
+
+- 关闭连接会话。
+
+WebNet 软件包可以实现**在浏览器访问设备端保存的页面,并且上传和下载设备端文件** 。WebNet 软件包使用流程基于 HTTP 协议的请求和响应过程,通过解析 HTTP 请求的类型和参数进行判断,执行相应的功能模块,并且正确返回 HTTP 响应数据,完成整个流程。
+
+下面以浏览器访问 WebNet 服务器根目录下主页面为例,介绍 WebNet 基本工作流程:
+
+1. **初始化 WebNet 软件包**
+
+```c
+int webnet_init(void);
+```
+
+WebNet 软件包使用之前需要先初始化,初始化函数中创建了 webnet 线程。该线程用于初始化开启的功能模块,完成创建服务器监听套接字,并使用监听套接字等待客户端连接和数据交互。 如下图为线程函数主要操作:
+
+```c
+/* WebNet 服务器线程处理函数 */
+static void webnet_thread(void *parameter)
+{
+    ....
+    /* 创建监听套接字 */
+    listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+    ...
+    /* 初始化开启的功能模块 */
+    webnet_module_handle_event(RT_NULL, WEBNET_EVENT_INIT);
+
+    /* 等待连接请求,等待接收数据 */
+    for (;;)
+    {
+        ...
+        sock_fd = select(maxfdp1, &tempfds, &tempwrtfds, 0, 0);
+        if (sock_fd == 0)
+            continue;
+
+        if (FD_ISSET(listenfd, &tempfds))
+        {
+            /* 处理新的连接请求 */
+        }
+        
+        /* 处理接收或发送的数据 */
+        webnet_sessions_handle_fds(&tempfds, &writeset);
+    }
+	...
+}
+```
+
+2. **接收连接请求,创建连接会话**
+
+   WebNet 初始化线程创建成功之后,当有新的连接请求产生时,会创建一个连接会话结构体,结构体定义如下:
+
+```c
+struct webnet_session
+{
+    struct webnet_session *next;          		  // 会话结构体链表
+
+    int socket;
+    struct sockaddr_in cliaddr;
+    struct webnet_request* request;				  // 会话的请求相关信息		
+
+    rt_uint16_t buffer_length;				    
+    rt_uint16_t buffer_offset;
+    rt_uint8_t  buffer[WEBNET_SESSION_BUFSZ];     // 会话缓冲区数据,用于接收请求数据
+    rt_uint32_t  session_phase;					  // 当前会话状态
+    rt_uint32_t  session_event_mask;
+    const struct webnet_session_ops* session_ops; // 会话事件执行函数 read、write、close等
+    rt_uint32_t user_data;
+};
+```
+
+   `webnet_session` 结构体用于存放当前建立的连接会话的部分信息,可用与当前会话连接的整个流程。在进行 HTTP 数据交互之前,需要先创建并初始化该结构体,**新会话的创建已经在 webnet 线程中完成**,如下所示:
+
+```c
+struct webnet_session* accept_session;
+accept_session = webnet_session_create(listenfd);
+if (accept_session == RT_NULL)
+{
+    /* 创建失败,关闭连接 */
+}
+```
+
+3. **接收 HTTP 请求数据,解析请求信息**
+    创建会话结构体成功之后,当连接会话接收到 HTTP 请求后,会对接收的 HTTP 请求进行处理,顺序地解析请求的类型、头部信息及附加参数。大致解析请求信息的流程如下所示:
+
+```c
+/* 该函数用于解析当前会话连接的请求模式、头部信息和参数 */
+static void _webnet_session_handle_read(struct webnet_session* session)
+{
+    /* 读取当前会话 HTTP 连接会话数据 */
+    ....
+
+    if (session->buffer_offset)
+    {
+        /* 解析 HTTP 请求模式(GET、POST 等)*/
+        if (session->session_phase == WEB_PHASE_METHOD)
+        {
+            webnet_request_parse_method(...);
+        }
+
+        /* 解析 HTTP 请求头部信息 */
+        if (session->session_phase == WEB_PHASE_HEADER)
+        {
+            webnet_request_parse_header(...);
+        }
+
+        /* 解析 HTTP URL 中附带请求参数 */
+        if (session->session_phase == WEB_PHASE_QUERY)
+        {
+            webnet_request_parse_post(...);
+        }
+    }
+}
+```
+
+4. **判断请求的功能模块,执行对应的功能**
+
+通过对请求模式和头部信息的解析,得到当前连接会话请求的基本信息,然后继续判断使用的功能模块的类型,并且执行对应的模块,判断的大致流程如下:
+
+```c
+/* 该函数为 WebNet 中用于判断和执行当前会话请求的功能,如日志功能、CGI 事件处理功能等 */
+static int _webnet_module_system_uri_physical(struct webnet_session* session, int event)
+{
+    /* 如果开启 LOG 功能模块,使用 LOG 日志输出功能 */
+#ifdef WEBNET_USING_LOG
+    webnet_module_log(session, event);
+#endif
+
+     /* 如果开启 ALIAS 功能模块,判断当前请求是否是别名请求 */
+#ifdef WEBNET_USING_ALIAS
+    result = webnet_module_alias(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+     /* 如果开启 AUTH 功能模块,判断当前请求是否需要执行基本认证操作 */
+#ifdef WEBNET_USING_AUTH
+    result = webnet_module_auth(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+    /* 如果开启 CGI 功能模块,判断当前请求是否需要执行 CGI 操作 */
+#ifdef WEBNET_USING_CGI
+    result = webnet_module_cgi(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+     ...
+}
+```
+
+5. **返回 HTTP 响应数据**
+
+判断功能模块类型成功,并且正确执对应功能之后,WebNet 服务器会对当前会话连接的请求给予响应,如 CGI 功能执行之后,在 CGI 执行函数中可以使用 `webnet_session_printf` 或 `webnet_session_write` 函数发送响应数据到客户端。
+
+```c
+/* 该函数 CGI 功能执行函数,当浏览器访问当前 CGI 事件时,执行该函数返回响应头部信息和数据 */
+static void cgi_hello_handler(struct webnet_session* session)
+{
+     /* 拼接需要发送的页面数据 */
+     ....
+     
+    /* 发送响应头部信息 */
+    webnet_session_set_header(session, mime_get_type(".html"), 200, "Ok", strlen(status));
+
+    /* 发送响应数据 */
+    webnet_session_write(session, (const rt_uint8_t*)status, rt_strlen(status));
+}
+```
+
+6. **关闭连接会话**
+
+当前会话连接请求解析成功、功能模块执行完成、响应数据发送完成之后,会关闭当前连接会话,释放会话结构体,完成整个 HTTP 数据数据交互过程,实现在浏览器上访问设备端提供的网页文件,或者完成上传、下载服务器上文件的操作。
+
+## 使用方式
+
+对于 WebNet 服务器的多种功能模块,有些功能模块在使用之前需要先设置对应配置参数,部分功能模块需要配合页面代码实现功能, 接下来将介绍 WebNet 服务器不同功能模块的使用方式。
+
+- **LOG 日志显示功能**
+
+开启之后可以显示会话请求的基本信息,比如连接设置的 IP 地址和端口号,HTTP 请求类型、访问地址等信息,建议调试代码时开启。
+
+- **AUTH 基本认证功能**
+
+Basic Authentication 基础认证功能可以按目录划分访问权限。需要在WebNet 服务器初始化之前调用 `webnet_auth_set` 函数设置目录的用户名和密码(设置的格式为 **用户名:密码**),浏览器中访问该目录时需要输入正确的用户名和密码才能访问目录。相关函数定义如下:
+
+```c
+/* 设置目录基本认证信息 */
+void webnet_auth_set(const char* path, const char* username_password);
+```
+
+AUTH 基本认证功能示例代码如下:
+
+```c
+void webnet_test(void)
+{
+    /* 设置 /admin 目录用户名为 admin 密码为 admin */
+     webnet_auth_set("/admin", "admin:admin");
+     webnet_init();
+}
+```
+
+/admin 目录设置基本认证功能之后,在浏览器中访问 /admin 目录时,需要输入设置的用户名和密码才能访问,如图所示:
+
+![基本认证功能](figures/auth.jpg)
+
+- **CGI 功能**
+
+CGI 功能可以自定义事件的执行函数,当浏览器发送对应该事件的请求时,WebNet 服务器可以执行相应的操作。需要在WebNet 服务器初始化之前调用 `webnet_cgi_register` 函数注册 CGI 执行函数,相关函数定义如下:
+
+```c
+/* 设置 CGI 事件根目录 */
+void webnet_cgi_set_root(const char* root);
+/* 设置 CGI 事件执行函数 */
+void webnet_cgi_register(const char* name, void (*handler)(struct webnet_session* session));
+
+/* 发送头部信息到 WebNet 连接的客户端,用于 CGI 事件执行函数中 */
+void webnet_session_set_header(struct webnet_session *session, const char* mimetype, int code, const char* status, int length);
+/* 发送固定格式数据到 WebNet 连接的客户端,用于 CGI 事件执行函数中 */
+void webnet_session_printf(struct webnet_session *session, const char* fmt, ...);
+/* 发送数据到 WebNet 连接的客户端,用于 CGI 事件执行函数中 */
+int  webnet_session_write(struct webnet_session *session, const rt_uint8_t* data, rt_size_t size);
+```
+
+CGI 功能使用的示例代码如下:
+
+```c
+static void cgi_hello_handler(struct webnet_session* session)
+{
+     const char* hello = "Hello World\n";
+     webnet_session_set_header(session, mime_get_type(".html"), 200, "Ok", strlen(status));
+     webnet_session_write(session, hello, rt_strlen(hello));
+     webnet_session_printf(session, "%s", hello);
+}
+void webnet_test(void)
+{
+    /* 设置 CGI 事件执行函数*/
+   webnet_cgi_register("hello", cgi_hello_handler);
+    webnet_init();
+}
+```
+
+对应的页面代码如下,浏览器上点击 **hello world** 按键将发送对应 CGI 请求给服务器。
+
+```c
+<html>
+  <body>
+    <hr>
+    <h3> CGI Test</h3>
+    WebNet CGI 功能可以让用户执行指定的函数,CGI测试:
+    <br/><br/>
+    <a href="/cgi-bin/hello">> hello world</a>
+    <br/>
+  </body>
+</html>
+```
+
+- **ASP 变量替换功能**
+
+ASP 变量替换功能,可以匹配网页代码中 **<% %>** 标记中包含的变量,替换成代码中注册的执行函数。所以在 WebNet 初始化之前需要调用 `webnet_asp_add_var` 设置 ASP 变量替换函数,相关函数定义如下:
+
+```
+/* 设置 ASP 变量执行函数 */
+void webnet_asp_add_var(const char* name, void (*handler)(struct webnet_session* session));
+```
+
+ASP 功能示例代码如下:
+
+```c
+static void asp_var_version(struct webnet_session* session)
+{
+    webnet_session_printf(session, "RT-Thread: %d.%d.%d\n", RT_VERSION, RT_SUBVERSION, RT_REVISION);
+}
+
+void webnet_test(void)
+{
+    /* 设置 ASP 变量执行函数*/
+    webnet_asp_add_var("version", asp_var_version);
+    webnet_init();
+}
+```
+
+对应的页面代码如下(文件名为 version.asp),访问该页面代码将 ASP 替换显示 RT-Thread 最新版本信息:
+
+```c
+<html>
+  <head>
+    <title> ASP Test </title>
+  </head>
+  <body>
+    <% version %>               /* ASP 变量替换成 RT_Thread 版本号显示 */
+  </body>
+</html>
+```
+
+- **SSI 文件嵌套功能**
+
+WebNet 中支持嵌入文本文件到网页中,页面中需要有 **<!--#include virtual="/xxx"-->** 或者 **<!--#include file="/xxx"-->** 标记存在将替换成对应的文件内容,SSI 功能页面一般以 **.shtml**、**.stm** 结尾,如下为示例页面代码(文件名为 index.shtml):
+
+```c
+<html>
+  <head>
+    <title> SSI Test </title>
+  </head>
+  <body>
+    <h1> SSI Test</h1>
+    <font size=\"+2\">The index.html page embedded in the following page</font>
+    <hr>
+    <!--#include virtual="/index.html" -->   /* 该页面这嵌入index.html 文件内容 */
+  </body>
+</html>
+```
+
+- **INDEX 目录文件显示功能**
+
+WebNet 服务器初始化成功之后,直接在浏览器中输入设置 IP 地址和要访问的目录,可以列出当前目录下所有的文件,如下图所示,访问服务器 /admin 目录,列出该目录下所有文件:
+
+![目录显示功能](figures/index_user.jpg)
+
+- **ALIAS 别名访问功能 **
+
+ALIAS 别名访问功能可以给文件夹设置别名访问。需要在 WebNet 服务器初始化之前设置该文件夹的别名,如下代码所示,调用 `webnet_alias_set` 函数设置 /test 目录的别名为 /admin,浏览器访问 /test 时会跳转访问到 /admin 目录:
+```c
+void webnet_test(void)
+{
+    /* 设置 /test 目录的别名为 /admin */
+    webnet_alias_set("/test", "/admin");
+    webnet_init();
+}
+```
+
+![别名访问功能](figures/alias_user.jpg)
+
+- **Upload 文件上传功能**
+
+Upload 文件上传功能用于上传本地文件到 WebNet 服务器指定目录中,上传文件之前需要创建并实现上传文件结构体,如下所示:
+
+```c
+struct webnet_module_upload_entry
+{
+    const char* url;                                         /* 文件上传的目录名 */
+
+    int (*upload_open) (struct webnet_session* session);     /* 打开文件 */
+    int (*upload_close)(struct webnet_session* session);     /* 关闭文件 */
+    int (*upload_write)(struct webnet_session* session, const void* data, rt_size_t length);    /* 写数据到文件 */
+    int (*upload_done) (struct webnet_session* session);     /* 下载完成 */
+};
+```
+
+该结构体定义上传文件的目录文件和需要使用的事件回调函数,如:打开文件、关闭文件、写数据到文件等。
+
+用户需要根据实际情况完成回调函数的实现,各回调函数中大致操作如下:
+
+- upload_open : 通过解析的文件名称,在指定的目录创建和打开文件;
+- upload_close:关闭文件;
+- upload_write:写数据到打开为文件中;
+- upload_done:文件上传成功之后,对浏览器返回响应数据。
+
+在回调函数实现的过程中,可能用到的获取当前上传文件会话信息的函数定义如下:
+
+```c
+/* 获取当前上传文件名称 */
+const char* webnet_upload_get_filename(struct webnet_session* session);
+/* 获取当前上传文件类型 */
+const char* webnet_upload_get_content_type(struct webnet_session* session);
+/* 获取当前上传文件打开之后的文件描述符 */
+const void* webnet_upload_get_userdata(struct webnet_session* session);
+```
+
+具体实现方式可以参考软件包 /samples/wn_sample_upload.c 中各个函数的实现方式。
+
+最后,在 WebNet 初始化之前需要调用 `webnet_upload_add` 函数设置上传文件的信息,如下代码所示:
+
+```c
+static int upload_open (struct webnet_session* session)
+{
+    /* 打开或新建文件 */
+}
+static int upload_close (struct webnet_session* session)
+{
+    /* 关闭文件 */
+}
+static int upload_write (struct webnet_session* session)
+{
+    /* 写数据到文件 */
+}
+static int upload_done (struct webnet_session* session)
+{
+    /* 下载完成,返回响应数据 */
+}
+const struct webnet_module_upload_entry upload_entry_upload =
+{
+    "/upload",
+    upload_open,
+    upload_close,
+    upload_write,
+    upload_done
+};
+
+void webnet_test(void)
+{
+    /* 注册文件上传执行函数 */
+     webnet_upload_add(&upload_entry_upload);
+     webnet_init();
+}
+```
+
+对应页面上传文件的代码如下:
+
+ ```c
+<html>
+  <body>
+    <h3>Upload File Test</h3>
+    文件上传模块可以用于上传文件到指定的目录,这里上传到根目录下的 /upload 目录。
+    <br/><br/>
+    <form name="upload" method="POST" enctype="multipart/form-data" action="/upload">
+      <input type="file" name="file1" >
+      <input type="submit" name="submit" value="上传">
+    </form>
+    <br/>
+    <a href="/upload/">点击浏览上传文件的目录</a>
+    <br/><br/>
+  </body>
+</html>
+ ```
+
+- **预压缩功能**
+
+WebNet 服务器预压缩功能,需要在服务器端提前压缩页面资源文件,生成以 .gz 后缀的压缩文件。以根目录下 index.html 页面为例,浏览器访问该页面文件时,如果存在名称为 **index.html.gz** 的压缩文件,将发送该压缩文件内容到浏览器,浏览器自动解析后显示原页面文件。
+
+使用预压缩功能时,需要先将压缩后的 **index.html.gz** 文件上传到文件系统中,如下图所示:
+
+![预压缩功能](figures/gzip.jpg)
+
+
+## 常见问题
+
+**1. 浏览器访问设备 IP 地址不显示页面信息**
+
+- 原因:设置的根目录地址错误;
+
+- 解决方法:确定设置的根目录地址和设备文件系统上创建的目录地址一致,确定根目录下有页面文件。
+
+**2. 设备出现 `out of pbuf` 错误情况**
+
+- 原因:设备内存不足;
+- 解决方式: WebNet 软件包上传文件等功能需要额外占用资源空间,建议在资源空间充足的设备上运行,或者在 qemu 上使用。

+ 6 - 0
docs/version.md

@@ -0,0 +1,6 @@
+# 版本和修订
+
+| Date       | Version   |  Author    | Note    |
+| --------   | :-----:   | :----      | :----   |
+| 2013-05-05 | v1.0.0    | bernard    | 初始版本 |
+| 2018-08-06 | v2.0.0    | chenyong   | 版本更新 |

+ 118 - 0
inc/webnet.h

@@ -0,0 +1,118 @@
+/*
+ * File      : webnet.h
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ *
+ * Release 1.0.3
+ * 2012-09-15     Bernard      fixed basic authentication issue in Safari.
+ * Release 1.0.4
+ * 2012-11-08     Bernard      fixed the buffer issue in FireFox upload.
+ * Release 1.0.5
+ * 2012-12-10     aozima       fixed small file upload issue.
+ * Release 1.0.6
+ * 2012-12-17     Bernard      fixed the content multi-transmission issue in POST.
+ * Release 1.0.7
+ * 2013-03-01     aozima       add feature: add Last-Modified and Cache-Control.
+ * Release 1.0.8
+ * 2015-04-17     Bernard      Use select for write socket
+ * Release 1.1.0
+ * 2015-05-01     aozima       support deflate gzip.
+ * Release 2.0.0
+ * 2016-07-31     Bernard      Rewrite http read handling and change version to 2.0.0
+ */
+
+#ifndef __WEBNET_H__
+#define __WEBNET_H__
+
+#include <rtthread.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef wn_malloc
+#define wn_malloc                      rt_malloc
+#endif
+
+#ifndef wn_free
+#define wn_free                        rt_free
+#endif
+
+#ifndef wn_realloc
+#define wn_realloc                     rt_realloc
+#endif
+
+#ifndef wn_strdup
+#define wn_strdup                      rt_strdup
+#endif
+
+#ifndef WEBNET_USING_RANGE
+#define WEBNET_USING_RANGE
+#endif
+    
+#ifndef WEBNET_USING_KEEPALIVE
+#define WEBNET_USING_KEEPALIVE
+#endif
+
+#ifndef WEBNET_USING_COOKIE
+#define WEBNET_USING_COOKIE
+#endif
+
+#define WEBNET_VERSION                 "2.0.0"      /* webnet version string */
+#define WEBNET_VERSION_NUM             0x20000      /* webnet version number */
+#define WEBNET_THREAD_NAME             "webnet"	    /* webnet thread name */
+
+#define WEBNET_THREAD_STACKSIZE        (4 * 1024)   /* webnet thread stack size */
+#define WEBNET_PRIORITY                20           /* webnet thread priority */
+#define WEBNET_PATH_MAX                256          /* maxiaml path length in webnet */
+#define WEBNET_SERVER                  "Server: webnet "WEBNET_VERSION"\r\n"
+
+/* Pre-declaration */
+struct webnet_session;
+/* webnet query item definitions */
+struct webnet_query_item
+{
+    char* name;
+    char* value;
+};
+
+/* get mimetype according to URL */
+const char* mime_get_type(const char* url);
+
+/* set and get listen socket port */
+void webnet_set_port(int port);
+int webnet_get_port(void);
+
+/* set and get root directory path */
+void webnet_set_root(const char* webroot_path);
+const char* webnet_get_root(void);
+
+/* webnet initialize */
+int webnet_init(void);
+
+#ifdef  __cplusplus
+    }
+#endif
+
+#endif /* __WEBNET_H__ */

+ 109 - 0
inc/wn_module.h

@@ -0,0 +1,109 @@
+/*
+ * File      : wn_module.h
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2011-08-06     Bernard      add webnet_alias_set declaration
+ * 2012-06-25     Bernard      add SSI and Upload module
+ */
+
+#ifndef __WN_MODULE_H__
+#define __WN_MODULE_H__
+
+#include <wn_session.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* initialization event */
+#define WEBNET_EVENT_INIT			(1 << 0)
+/* map uri request to physical url */
+#define WEBNET_EVENT_URI_PHYSICAL	(1 << 1)
+/* uri request */
+#define WEBNET_EVENT_URI_POST		(1 << 2)
+/* header of response */
+#define WEBNET_EVENT_RSP_HEADER		(1 << 3)
+/* file content of response */
+#define WEBNET_EVENT_RSP_FILE		(1 << 4)
+
+
+/* continue other modules */
+#define WEBNET_MODULE_CONTINUE		0
+/* this session is finished */
+#define WEBNET_MODULE_FINISHED		1
+
+int webnet_module_handle_event(struct webnet_session* session, int event);
+int webnet_module_system_dofile(struct webnet_session* session);
+int webnet_module_handle_uri(struct webnet_session* session);
+
+/* module function pre-declaration */
+int webnet_module_alias(struct webnet_session* sesion, int event);
+int webnet_module_auth(struct webnet_session* session, int event);
+int webnet_module_asp(struct webnet_session* session, int event);
+int webnet_module_cgi(struct webnet_session* session, int event);
+int webnet_module_dirindex(struct webnet_session* session, int event);
+int webnet_module_log(struct webnet_session* session, int event);
+int webnet_module_lua(struct webnet_session* sesion, int event);
+int webnet_module_ssl(struct webnet_session* sesion, int event);
+int webnet_module_ssi(struct webnet_session* session, int event);
+int webnet_module_dav(struct webnet_session* session, int event);
+int webnet_module_dmr(struct webnet_session* session, int event);
+
+/* add ASP variable */
+void webnet_asp_add_var(const char* name, void (*handler)(struct webnet_session* session));
+/* register CGI event */
+void webnet_cgi_register(const char* name, void (*handler)(struct webnet_session* session));
+void webnet_cgi_set_root(const char* root);
+/* set basic authentication configure */
+void webnet_auth_set(const char* path, const char* username_password);
+/* set directory alias */
+void webnet_alias_set(char* old_path, char* new_path);
+
+/* upload module */
+struct webnet_module_upload_entry
+{
+    const char* url;
+
+    int (*upload_open) (struct webnet_session* session);
+    int (*upload_close)(struct webnet_session* session);
+    int (*upload_write)(struct webnet_session* session, const void* data, rt_size_t length);
+    int (*upload_done) (struct webnet_session* session);
+};
+int webnet_module_upload(struct webnet_session* session, int event);
+void webnet_upload_add(const struct webnet_module_upload_entry* entry);
+
+const char* webnet_upload_get_filename(struct webnet_session* session);
+const char* webnet_upload_get_content_type(struct webnet_session* session);
+const char* webnet_upload_get_nameentry(struct webnet_session* session, const char* name);
+const void* webnet_upload_get_userdata(struct webnet_session* session);
+
+int webnet_upload_file_open(struct webnet_session* session);
+int webnet_upload_file_close(struct webnet_session* session);
+int webnet_upload_file_write(struct webnet_session* session, const void* data, rt_size_t length);
+
+#ifdef  __cplusplus
+    }
+#endif
+
+#endif /* __WN_MODULE_H__ */

+ 135 - 0
inc/wn_request.h

@@ -0,0 +1,135 @@
+/*
+ * File      : wn_request.h
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2011-08-18     Bernard      added content length field
+ */
+
+#ifndef __WN_REQUEST_H__
+#define __WN_REQUEST_H__
+
+#include <rtthread.h>
+
+#include <wn_session.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* http request method */
+enum webnet_method
+{
+    WEBNET_UNKNOWN = 0,
+    WEBNET_GET,
+    WEBNET_POST,
+    WEBNET_HEADER,
+    WEBNET_HEAD,
+	WEBNET_PUT,
+	WEBNET_OPTIONS,
+	WEBNET_PROPFIND,
+	WEBNET_PROPPATCH,
+	WEBNET_DELETE,
+	WEBNET_CONNECT,
+	WEBNET_MKCOL,
+	WEBNET_MOVE,
+    WEBNET_SUBSCRIBE,
+    WEBNET_UNSUBSCRIBE,
+    WEBNET_NOTIFY,
+};
+
+/* http connection status */
+enum webnet_connection
+{
+    WEBNET_CONN_CLOSE,
+    WEBNET_CONN_KEEPALIVE,
+};
+
+/* http request structure */
+struct webnet_request
+{
+    enum webnet_method method;
+    int result_code;
+    int content_length;
+
+    /* the corresponding session */
+    struct webnet_session *session;
+
+    /* path and authorization */
+    char* path;
+    char* host;
+    char* authorization;
+#if WEBNET_CACHE_LEVEL > 0
+    char* modified;
+#endif /* WEBNET_CACHE_LEVEL */
+#ifdef WEBNET_USING_GZIP
+    rt_bool_t support_gzip;
+#endif /* WEBNET_USING_GZIP */
+
+    char* user_agent;
+    char* accept_language;
+    char* cookie;
+    char* referer;
+#ifdef WEBNET_USING_RANGE
+    char *Range;
+    size_t pos_start;
+    size_t pos_end;
+#endif /* WEBNET_USING_RANGE */
+#ifdef WEBNET_USING_DAV
+	char* depth;
+	char* destination;
+#endif /* WEBNET_USING_DAV */
+
+    /* DMR */
+    char *soap_action;
+    char *callback;
+    char *sid;
+
+    /* Content-Type */
+    char* content_type;
+
+    /* query information */
+    char* query;
+	int query_offset;
+    struct webnet_query_item* query_items;
+    rt_uint16_t query_counter;
+
+    enum webnet_connection connection;
+
+    /* whether the string filed is copied */
+    rt_bool_t field_copied;
+};
+
+struct webnet_request* webnet_request_create(void);
+void webnet_request_destory(struct webnet_request* request);
+
+int webnet_request_parse_method(struct webnet_request *request, char* buffer, int length);
+int webnet_request_parse_header(struct webnet_request *request, char* buffer, int length);
+int webnet_request_parse_post(struct webnet_request* request, char* buffer, int length);
+
+void webnet_request_parse(struct webnet_request* request, char* buffer, int length);
+
+rt_bool_t webnet_request_has_query(struct webnet_request* request, char* name);
+const char* webnet_request_get_query(struct webnet_request* request, char* name);
+
+#ifdef  __cplusplus
+    }
+#endif
+
+#endif /* __WN_REQUEST_H__ */

+ 108 - 0
inc/wn_session.h

@@ -0,0 +1,108 @@
+/*
+ * File      : wn_session.h
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ */
+
+#ifndef __WN_SESSION_H__
+#define __WN_SESSION_H__
+
+#include <wn_request.h>
+
+#ifdef RT_USING_SAL
+#include <sys/socket.h>
+#else 
+#include <lwip/sockets.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define WEBNET_SESSION_BUFSZ	(4 * 1024)
+
+/* close session */
+#define WEBNET_EVENT_CLOSE			(1 << 5)
+/* read  session */
+#define WEBNET_EVENT_READ			(1 << 6)
+/* write session */
+#define WEBNET_EVENT_WRITE			(1 << 7)
+
+
+struct webnet_session_ops
+{
+    void (*session_handle)(struct webnet_session* session, int event);
+    void (*session_close) (struct webnet_session* session);
+};
+
+enum webnet_session_phase
+{
+	WEB_PHASE_METHOD = 0, 	/* parse method */
+	WEB_PHASE_HEADER, 		/* handle web request header */
+	WEB_PHASE_QUERY,        /* handle web query */
+	WEB_PHASE_RESPONSE,		/* handle web response */
+	WEB_PHASE_CLOSE,		/* to close session */
+};
+
+struct webnet_session
+{
+    struct webnet_session *next;
+
+    /* socket information */
+    int socket;
+    struct sockaddr_in cliaddr;
+
+    /* webnet request */
+    struct webnet_request* request;
+
+    /* session buffer */
+    rt_uint16_t buffer_length;
+    rt_uint16_t buffer_offset;
+    rt_uint8_t  buffer[WEBNET_SESSION_BUFSZ];
+
+	/* session phase */
+	rt_uint32_t  session_phase;
+
+	rt_uint32_t  session_event_mask;
+    const struct webnet_session_ops* session_ops;
+    rt_uint32_t user_data;
+};
+
+struct webnet_session* webnet_session_create(int listenfd);
+
+int  webnet_session_read(struct webnet_session *session, char *buffer, int length);
+void webnet_session_close(struct webnet_session *session);
+
+void webnet_session_printf(struct webnet_session *session, const char* fmt, ...);
+int  webnet_session_write(struct webnet_session *session, const rt_uint8_t* data, rt_size_t size);
+int  webnet_session_redirect(struct webnet_session *session, const char* url);
+int  webnet_session_get_physical_path(struct webnet_session *session, const char* virtual_path, char* full_path);
+void webnet_session_set_header(struct webnet_session *session, const char* mimetype, int code, const char* status, int length);
+void webnet_session_set_header_status_line(struct webnet_session *session, int code, const char * reason_phrase);
+
+int webnet_sessions_set_fds(fd_set *readset, fd_set *writeset);
+void webnet_sessions_handle_fds(fd_set *readset, fd_set *writeset);
+
+#ifdef  __cplusplus
+    }
+#endif
+
+#endif /* __WN_SESSION_H__ */
+

+ 47 - 0
inc/wn_utils.h

@@ -0,0 +1,47 @@
+/*
+ * File      : wn_utils.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ */
+
+#ifndef __WN_UTILS_H__
+#define __WN_UTILS_H__
+
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int str_begin_with(const char *s, const char *t);
+int str_end_with(const char* s, const char* t);
+int str_path_with(const char *s, const char *t);
+char *str_decode_path(char *path);
+char *str_base64_encode(const char* src);
+char* str_normalize_path(char* fullpath);
+char * urlencode(const char *str, int len, int *new_length);
+int urldecode(char *str, int len);
+
+#ifdef  __cplusplus
+    }
+#endif
+
+#endif /* __WN_UTILS_H__ */
+

+ 98 - 0
module/wn_module_alias.c

@@ -0,0 +1,98 @@
+/*
+ * File      : wn_module_alias.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ */
+
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef WEBNET_USING_ALIAS
+
+struct webnet_alias_item
+{
+    char* old_path;
+    char* new_path;
+};
+static struct webnet_alias_item *_alias_items = RT_NULL;
+static rt_uint32_t _alias_item_count = 0;
+
+void webnet_alias_set(char* old_path, char* new_path)
+{
+    if (_alias_items == RT_NULL)
+    {
+        _alias_item_count = 1;
+        _alias_items = (struct webnet_alias_item*)wn_malloc (sizeof(struct webnet_alias_item) *
+                       _alias_item_count);
+    }
+    else
+    {
+        _alias_item_count += 1;
+        _alias_items = (struct webnet_alias_item*) wn_realloc (_alias_items, sizeof(struct webnet_alias_item) *
+                       _alias_item_count);
+    }
+
+    RT_ASSERT(_alias_items != RT_NULL);
+
+    _alias_items[_alias_item_count - 1].old_path = wn_strdup(old_path);
+    RT_ASSERT(_alias_items[_alias_item_count - 1].old_path != RT_NULL);
+    _alias_items[_alias_item_count - 1].new_path = wn_strdup(new_path);
+    RT_ASSERT(_alias_items[_alias_item_count - 1].new_path != RT_NULL);
+}
+RTM_EXPORT(webnet_alias_set);
+
+int webnet_module_alias(struct webnet_session* session, int event)
+{
+    if (event == WEBNET_EVENT_URI_PHYSICAL)
+    {
+        int index;
+        struct webnet_request* request;
+
+        RT_ASSERT(session != RT_NULL);
+        request = session->request;
+        RT_ASSERT(request != RT_NULL);
+
+        /* check whether the uri is a alias */
+        for (index = 0; index < _alias_item_count; index ++)
+        {
+            if (str_path_with(request->path, _alias_items[index].old_path))
+            {
+                char* map_path;
+                map_path = (char*) wn_malloc (WEBNET_PATH_MAX);
+                RT_ASSERT(map_path != RT_NULL);
+
+                rt_snprintf(map_path, WEBNET_PATH_MAX, "%s/%s",
+                            _alias_items[index].new_path,
+                            request->path + strlen(_alias_items[index].old_path));
+
+                /* set new path */
+                wn_free(request->path);
+                request->path = map_path;
+
+                return WEBNET_MODULE_CONTINUE;
+            }
+        }
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#endif /* WEBNET_USING_ALIAS */

+ 287 - 0
module/wn_module_asp.c

@@ -0,0 +1,287 @@
+/*
+ * File      : wn_module_asp.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ */
+
+#include <string.h>
+#include <rtthread.h>
+
+#ifdef RT_USING_DFS
+#include <dfs_posix.h>
+#endif
+
+#ifdef RT_USING_SAL
+#include <sys/socket.h>
+#else
+#include <lwip/netif.h>
+#endif /* RT_USING_SAL */
+
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef WEBNET_USING_ASP
+
+struct webnet_asp_variable
+{
+    char* name;
+    void (*handler)(struct webnet_session* session);
+};
+static struct webnet_asp_variable* _webnet_asp_vars = RT_NULL;
+rt_uint32_t _webnet_asp_vars_count = 0;
+
+void webnet_asp_add_var(const char* name, void (*handler)(struct webnet_session* session))
+{
+    if (_webnet_asp_vars == RT_NULL)
+    {
+        _webnet_asp_vars_count = 1;
+        _webnet_asp_vars = (struct webnet_asp_variable*)wn_malloc (sizeof(struct webnet_asp_variable) *
+                           _webnet_asp_vars_count);
+    }
+    else
+    {
+        _webnet_asp_vars_count += 1;
+        _webnet_asp_vars = (struct webnet_asp_variable*) wn_realloc (_webnet_asp_vars, sizeof(struct webnet_asp_variable) *
+                           _webnet_asp_vars_count);
+    }
+
+    RT_ASSERT(_webnet_asp_vars != RT_NULL);
+
+    _webnet_asp_vars[_webnet_asp_vars_count - 1].name = wn_strdup(name);
+    _webnet_asp_vars[_webnet_asp_vars_count - 1].handler = handler;
+}
+RTM_EXPORT(webnet_asp_add_var);
+
+static void _default_asp_handler(struct webnet_session* session, const char* name)
+{
+    if (strncmp(name, "VERION", 6) == 0)
+    {
+        webnet_session_printf(session, "WebNet 1.0.0");
+    }
+    else if (strncmp(name, "REMOTE_ADDR", 11) == 0)
+    {
+        webnet_session_printf(session, "%s", inet_ntoa(session->cliaddr.sin_addr));
+    }
+    else if (strncmp(name, "REMOTE_PORT", 11) == 0)
+    {
+        webnet_session_printf(session, "%d", ntohs(session->cliaddr.sin_port));
+    }
+    else if (strncmp(name, "SERVER_ADDR", 11) == 0)
+    {
+#if defined(RT_USING_LWIP) && !defined(RT_USING_SAL)
+        webnet_session_printf(session, "%s", inet_ntoa(*(struct in_addr*)&(netif_default->ip_addr)));
+#endif
+    }
+    else if (strncmp(name, "SERVER_PORT", 11) == 0)
+    {
+        webnet_session_printf(session, "%d", WEBNET_PORT);
+    }
+    else if (strncmp(name, "DOCUMENT_ROOT", 13) == 0)
+    {
+        webnet_session_printf(session, "%s", webnet_get_root());
+    }
+    else if (strncmp(name, "SERVER", 6) == 0)
+    {
+        webnet_session_printf(session, "RT-Thread/WebNet");
+    }
+    else if (strncmp(name, "HOST", 4) == 0)
+    {
+        webnet_session_printf(session, "WebNet");
+    }
+    else if (strncmp(name, "DATE", 4) == 0)
+    {
+        webnet_session_printf(session, "2011/08/01");
+    }
+    else if (strncmp(name, "USER_AGENT", 10) == 0)
+    {
+        webnet_session_printf(session, "%s", session->request->user_agent);
+    }
+    else if (strncmp(name, "COOKIE", 6) == 0)
+    {
+        webnet_session_printf(session, "%s", session->request->cookie);
+    }
+    else if (strncmp(name, "MEMUSAGE", 8) == 0)
+    {
+        rt_uint32_t total, used, max_used;
+
+        //rt_memory_info(&total, &used, &max_used);
+        total = 1024*32;
+        used = 1024*16;
+        max_used = 1024*24;
+		
+        webnet_session_printf(session, "current %d/maximal used %d/total %d", used, max_used, total);
+    }
+    else if (strncmp(name, "TICK", 4) == 0)
+    {
+        rt_uint32_t tick;
+        rt_uint32_t hour, min, second;
+
+        tick = rt_tick_get()/RT_TICK_PER_SECOND;
+        second = tick % 60;
+        min    = (tick/60) % 60;
+        hour   = tick / (60 * 60);
+
+        webnet_session_printf(session, "%02d:%02d:%02d", hour, min, second);
+    }
+}
+
+static void _webnet_asp_dofile(struct webnet_session* session, int fd)
+{
+    char *asp_begin, *asp_end;
+    char *offset, *end;
+    char *buffer;
+    rt_uint32_t length, index;
+
+    /* get file length */
+    length = lseek(fd, 0, SEEK_END);
+    lseek(fd, 0, SEEK_SET);
+
+    /* allocate read buffer */
+    buffer = (char*) wn_malloc (length);
+    if (buffer == RT_NULL)
+    {
+        session->request->result_code = 500;
+        return;
+    }
+
+    /* write page header */
+    webnet_session_set_header(session, "text/html", 200, "OK", -1);
+    /* read file to buffer */
+    if (read(fd, buffer, (size_t)length) != length) /* read failed */
+    {
+        wn_free(buffer);
+        session->request->result_code = 500;
+        return;
+    }
+
+    offset = buffer;
+    end = buffer + length;
+    while (offset < end)
+    {
+        /* get beginning of asp variable */
+        asp_begin = strstr(offset, "<%");
+        if (asp_begin == RT_NULL)
+        {
+            /* write content directly */
+            webnet_session_write(session, (const rt_uint8_t*)offset, end - offset);
+            break;
+        }
+
+        /* get end of aps variable */
+        asp_end = strstr(asp_begin, "%>");
+        if (asp_end == RT_NULL)
+        {
+            /* write content directly */
+            webnet_session_write(session, (const rt_uint8_t*)offset, end - offset);
+            break;
+        }
+        else
+        {
+            /* write content */
+            webnet_session_write(session, (const rt_uint8_t*)offset, asp_begin - offset);
+
+            offset = asp_begin + 2;
+            while ((*offset == ' ') || (*offset == '\t')) offset ++;
+
+            /* extrace asp variable */
+            for (index = 0; index < _webnet_asp_vars_count; index ++)
+            {
+                if (str_begin_with(offset, _webnet_asp_vars[index].name))
+                {
+                    /* found asp variable */
+                    _webnet_asp_vars[index].handler(session);
+                    break;
+                }
+            }
+            if (index == _webnet_asp_vars_count)
+            {
+                /* use default handler */
+                _default_asp_handler(session, offset);
+            }
+
+            /* move to the end of asp varaialbe */
+            offset = asp_end + 2;
+        }
+    }
+
+    /* release read buffer */
+    wn_free(buffer);
+}
+
+int webnet_module_asp(struct webnet_session* session, int event)
+{
+    struct webnet_request* request;
+
+    RT_ASSERT(session != RT_NULL);
+    request = session->request;
+    RT_ASSERT(request != RT_NULL);
+
+    if (event == WEBNET_EVENT_URI_POST)
+    {
+        int fd;
+
+        /* check whether a asp file */
+        if ((strstr(request->path, ".asp") != RT_NULL) ||
+                (strstr(request->path, ".ASP") != RT_NULL))
+        {
+            /* try to open this file */
+
+            fd = open(request->path, O_RDONLY, 0);
+            if ( fd >= 0)
+            {
+                _webnet_asp_dofile(session, fd);
+                close(fd);
+                return WEBNET_MODULE_FINISHED;
+            }
+            else
+            {
+                /* no this file */
+                request->result_code = 404;
+                return WEBNET_MODULE_FINISHED;
+            }
+        }
+        else
+        {
+            /* try index.asp */
+            char *asp_filename;
+
+            asp_filename = (char*) wn_malloc (WEBNET_PATH_MAX);
+            if (asp_filename != RT_NULL)
+            {
+                rt_snprintf(asp_filename, WEBNET_PATH_MAX, "%s/index.asp", request->path);
+                fd = open(asp_filename, O_RDONLY, 0);
+
+                if (fd >= 0)
+                {
+                    wn_free(asp_filename);
+                    _webnet_asp_dofile(session, fd);
+                    close(fd);
+                    return WEBNET_MODULE_FINISHED;
+                }
+            }
+            wn_free(asp_filename);
+        }
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#endif /* WEBNET_USING_ASP */

+ 136 - 0
module/wn_module_auth.c

@@ -0,0 +1,136 @@
+/*
+ * File      : wn_module_auth.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2011-11-16     Bernard      added password modification feature.
+ * 2011-11-19     Bernard      fixed the auth string length issue.
+ * 2012-06-25     Bernard      fixed the authorization is NULL issue.
+ */
+
+#include <string.h>
+
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef WEBNET_USING_AUTH
+
+struct webnet_auth_item
+{
+    char *path;
+
+    /* username and password, which will encode as base64 as - username:password*/
+    char *username_password;
+};
+static struct webnet_auth_item* _auth_items = RT_NULL;
+static rt_uint32_t _auth_items_count = 0;
+
+/**
+ * set the authorization on the path
+ *
+ * @param path the path to be authorized
+ * @param username_password the user and password, which format shall be
+ * username:password
+ */
+void webnet_auth_set(const char* path, const char* username_password)
+{
+    if (_auth_items == RT_NULL)
+    {
+        _auth_items_count = 1;
+        _auth_items = (struct webnet_auth_item*)wn_malloc (sizeof(struct webnet_auth_item) *
+                      _auth_items_count);
+    }
+    else
+    {
+        rt_ubase_t index;
+
+        /* check whether modify a password */
+        for (index = 0; index < _auth_items_count; index ++)
+        {
+            if (strcmp(path, _auth_items[index].path) == 0)
+            {
+                wn_free(_auth_items[index].username_password);
+                _auth_items[index].username_password = str_base64_encode(username_password);
+                return;
+            }
+        }
+
+        _auth_items_count += 1;
+        _auth_items = (struct webnet_auth_item*) wn_realloc (_auth_items, sizeof(struct webnet_auth_item) *
+                      _auth_items_count);
+    }
+
+    RT_ASSERT(_auth_items != RT_NULL);
+
+    _auth_items[_auth_items_count - 1].path = wn_strdup(path);
+    _auth_items[_auth_items_count - 1].username_password = str_base64_encode(username_password);
+}
+RTM_EXPORT(webnet_auth_set);
+
+/**
+ * Authorization module handler
+ */
+int webnet_module_auth(struct webnet_session* session, int event)
+{
+    if (event == WEBNET_EVENT_URI_PHYSICAL)
+    {
+        rt_uint32_t index;
+        struct webnet_request *request;
+
+        RT_ASSERT(session != RT_NULL);
+        request = session->request;
+        RT_ASSERT(request != RT_NULL);
+
+        /* check authorization item */
+        for (index = 0; index < _auth_items_count; index ++)
+        {
+            if (str_path_with(request->path, _auth_items[index].path))
+            {
+                if (request->authorization == RT_NULL ||
+                        rt_strlen(_auth_items[index].username_password) !=
+                        rt_strlen(request->authorization))
+                {
+                    /* set authorization request, 401 */
+                    request->result_code = 401;
+                    return WEBNET_MODULE_FINISHED;
+                }
+
+                /* check authorization */
+                if (strcmp(request->authorization,
+                           _auth_items[index].username_password) == 0)
+                {
+                    /* authorization OK */
+                    request->result_code = 200;
+                    return WEBNET_MODULE_CONTINUE;
+                }
+                else
+                {
+                    /* set authorization request, 401 */
+                    request->result_code = 401;
+                    return WEBNET_MODULE_FINISHED;
+                }
+            }
+        }
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#endif /* WEBNET_USING_AUTH */

+ 128 - 0
module/wn_module_cgi.c

@@ -0,0 +1,128 @@
+/*
+ * File      : wn_module_cgi.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ */
+
+#include <string.h>
+
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef RT_USING_DFS
+#include <dfs_posix.h>
+#endif
+
+#ifdef WEBNET_USING_CGI
+
+#define CGI_ROOT_PATH_MAX	64
+
+static char _cgi_root[CGI_ROOT_PATH_MAX] = {0};
+struct webnet_cgi_item
+{
+    const char* name;
+    void (*handler)(struct webnet_session* session);
+};
+static struct webnet_cgi_item* _cgi_items = RT_NULL;
+static rt_uint32_t _cgi_count = 0;
+
+void webnet_cgi_set_root(const char* root)
+{
+    if (strlen(root) > CGI_ROOT_PATH_MAX)
+    {
+        RT_ASSERT(0);
+        return;
+    }
+
+    strncpy(_cgi_root, root, strlen(root));
+    if (_cgi_root[strlen(_cgi_root)] != '/')
+    {
+        _cgi_root[strlen(_cgi_root) + 1] = '/';
+        _cgi_root[strlen(_cgi_root) + 1] = '\0';
+    }
+}
+RTM_EXPORT(webnet_cgi_set_root);
+
+void webnet_cgi_register(const char* name, void (*handler)(struct webnet_session* session))
+{
+    if (_cgi_items == RT_NULL)
+    {
+        _cgi_count = 1;
+        _cgi_items = (struct webnet_cgi_item*) wn_malloc (sizeof(struct webnet_cgi_item) * _cgi_count);
+    }
+    else
+    {
+        _cgi_count += 1;
+        _cgi_items = (struct webnet_cgi_item*) wn_realloc (_cgi_items, sizeof(struct webnet_cgi_item) * _cgi_count);
+    }
+
+    RT_ASSERT(_cgi_items != RT_NULL);
+    _cgi_items[_cgi_count - 1].name = name;
+    _cgi_items[_cgi_count - 1].handler = handler;
+}
+RTM_EXPORT(webnet_cgi_register);
+
+int webnet_module_cgi(struct webnet_session* session, int event)
+{
+    if (event == WEBNET_EVENT_INIT)
+    {
+        /* set default cgi path */
+        if (_cgi_root[0] == '\0')
+        {
+            strcpy(_cgi_root, "/cgi-bin/");
+        }
+    }
+    else if (event == WEBNET_EVENT_URI_PHYSICAL)
+    {
+        struct webnet_request* request;
+        char *cgi_path = RT_NULL;
+
+        RT_ASSERT(session != RT_NULL);
+        request = session->request;
+        RT_ASSERT(request != RT_NULL);
+        /* check whether a cgi request */
+        cgi_path = strstr(request->path, _cgi_root);
+        if (cgi_path != RT_NULL)
+        {
+            char* cgi_name;
+            rt_uint32_t index;
+
+            cgi_name = cgi_path + strlen(_cgi_root);
+            for (index = 0; index < _cgi_count; index ++)
+            {
+                if ((strlen(cgi_name) == strlen(_cgi_items[index].name))
+                        && strncasecmp(cgi_name, _cgi_items[index].name, strlen(_cgi_items[index].name)) == 0)
+                {
+                    /* found it */
+                    _cgi_items[index].handler(session);
+                    return WEBNET_MODULE_FINISHED;
+                }
+            }
+
+            /* set 404 not found error */
+            request->result_code = 404;
+        }
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#endif /* WEBNET_USING_CGI */

+ 501 - 0
module/wn_module_dav.c

@@ -0,0 +1,501 @@
+/*
+ * File      : wn_module_dav.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2015-9-9       Coing        the first version
+ */
+
+#include <webnet.h>
+#include <wn_session.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef RT_USING_DFS
+#include <dfs_posix.h>
+#endif
+
+#define DBG_ENABLE
+#define DBG_COLOR
+#define DBG_SECTION_NAME    "wn.dav"
+#ifdef WEBNET_USING_LOG
+#define DBG_LEVEL           DBG_LOG
+#else
+#define DBG_LEVEL           DBG_INFO
+#endif /* WEBNET_USING_LOG */
+#include <rtdbg.h>
+
+#ifdef WEBNET_USING_DAV
+
+struct webnet_module_put_entry
+{
+    int (*put_open) (struct webnet_session* session);
+    int (*put_close)(struct webnet_session* session);
+    int (*put_write)(struct webnet_session* session, const void* data, rt_size_t length);
+    int (*put_done) (struct webnet_session* session);
+};
+
+struct webnet_module_put_session
+{
+    rt_uint16_t file_opened;
+
+    /* put entry */
+    const struct webnet_module_put_entry* entry;
+
+    rt_uint32_t user_data;
+};
+
+static const char*  propfind_element ="<d:response>"
+                                       "<d:href>%s</d:href>"
+                                       "<d:propstat>"
+                                       "<d:prop>"
+                                       "<d:resourcetype>%s</d:resourcetype>"
+                                       "<d:getcontentlength>%d</d:getcontentlength>"
+                                       "<d:getlastmodified>%s</d:getlastmodified>" // Sat, 05 Sep 2015 09:47:53 GMT
+                                       "</d:prop>"
+                                       "<d:status>HTTP/1.1 200 OK</d:status>"
+                                       "</d:propstat>"
+                                       "</d:response>\n";
+
+static const void* webnet_put_get_userdata(struct webnet_session* session);
+int webnet_module_put_method(struct webnet_session* session);
+static void print_propfind_element(struct webnet_session *session, const char *uri,struct stat *stp);
+
+int webnet_module_dav(struct webnet_session* session, int event)
+{
+    if (event != WEBNET_EVENT_URI_POST)
+        return WEBNET_MODULE_CONTINUE;
+
+    if(session->request->method == WEBNET_OPTIONS)
+    {
+        static const char* status = "Allow: GET, POST, HEAD, PUT, DELETE, OPTIONS, PROPFIND, MKCOL\r\nDAV: 1\r\n\r\n";
+
+        LOG_D("OPTIONS %s", session->request->path);
+
+        session->request->result_code = 200;
+        webnet_session_set_header_status_line(session, session->request->result_code, "OK");
+        webnet_session_printf(session, status);
+
+        return WEBNET_MODULE_FINISHED;
+    }
+    else if(session->request->method == WEBNET_PROPFIND)
+    {
+        int fd;
+        DIR *dir;
+        rt_uint8_t exit_file = 0;
+        struct stat file_stat;
+
+        const char* parent_path;
+
+        static const char header[] = "HTTP/1.1 207 Multi-Status\r\n"
+                                     "Connection: close\r\n"
+                                     "Content-Type: text/xml; charset=utf-8\r\n\r\n"
+                                     "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+                                     "<d:multistatus xmlns:d='DAV:'>\n";
+
+        static const char footer[] = "</d:multistatus>";
+
+        char *depth = session->request->depth;
+
+        LOG_D("PROPFIND %s depth: %s", session->request->path,
+            session->request->depth?session->request->depth:"null");
+
+        fd = open(session->request->path, O_RDONLY, 0);
+        if (fd >= 0)
+        {
+            close(fd);
+            exit_file = 1;
+        }
+        dir = opendir(session->request->path);
+        if(dir != RT_NULL)
+        {
+            closedir(dir);
+            exit_file = 1;
+        }
+        if(!exit_file)
+        {
+            LOG_E("Open file(%s) is not exist.", session->request->path);
+            session->request->result_code = 404;
+            return WEBNET_MODULE_FINISHED;
+        }
+
+        webnet_session_printf(session, header);
+
+        parent_path = session->request->path + strlen(webnet_get_root());
+
+        /* output the parent. */
+        file_stat.st_size = 0;
+        file_stat.st_mtime = 0;
+        file_stat.st_mode = S_IFDIR;
+        print_propfind_element(session, parent_path, &file_stat);
+
+        /* depth: 0, 1, infinity. */
+        if( (depth == NULL || (strcmp(depth, "0") != 0) ) )
+        {
+            struct dirent* dirent;
+            char *fullpath;
+
+            fullpath = wn_malloc (WEBNET_PATH_MAX);
+            rt_memset(&file_stat, 0, sizeof(struct stat));
+
+            //eg. dir = opendir("/webnet/SD");
+            dir = opendir(session->request->path);
+
+            while((dirent = readdir(dir)) != NULL)
+            {
+                /* build full path for each file */
+                rt_sprintf(fullpath, "%s/%s",session->request->path, dirent->d_name);
+
+                str_normalize_path(fullpath);
+
+                stat(fullpath, &file_stat);
+
+                print_propfind_element(session, dirent->d_name,&file_stat);
+            }
+            closedir(dir);
+            wn_free(fullpath);
+        }
+
+        webnet_session_printf(session,footer);
+
+        return WEBNET_MODULE_FINISHED;
+    }
+    else if(session->request->method == WEBNET_PUT)
+    {
+        LOG_D("PUT %s", session->request->path);
+        return webnet_module_put_method(session);
+    }
+    else if(session->request->method == WEBNET_PROPPATCH)
+    {
+        int proppatch_length = 0;
+
+        static const char Proppatch_header[] = "HTTP/1.1 207 Multi-Status\r\n"
+                                               //"Date: Mon, 07 Sep 2015 01:50:42 GMT\r\n"
+                                               "Server: %s %s\r\n"
+                                               "Content-Length: %d \r\n"
+                                               "Content-Type: text/xml; charset=\"utf-8\"\r\n\r\n";
+
+        static const char Proppatch_ex[] =  "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
+                                            "<D:multistatus xmlns:D=\"DAV:\" xmlns:ns0=\"DAV:\">"
+                                            "<D:response>"
+                                            "<D:href>%s</D:href>"
+                                            "<D:propstat>"
+                                            "<D:prop><ns0:getlastmodified/>"
+                                            "</D:prop>"
+                                            "<D:status>HTTP/1.1 409 (status)</D:status>"
+                                            "<D:responsedescription>"
+                                            "Property is read-only.</D:responsedescription>"
+                                            "</D:propstat>"
+                                            "</D:response>"
+                                            "</D:multistatus>";
+
+        LOG_D("PROPPATCH %s", session->request->path);
+
+        proppatch_length = strlen(Proppatch_ex)+strlen(session->request->path)-2;
+
+        webnet_session_printf(session,Proppatch_header,WEBNET_THREAD_NAME,WEBNET_VERSION,proppatch_length);
+
+        webnet_session_printf(session,Proppatch_ex,session->request->path);
+
+        return WEBNET_MODULE_FINISHED;
+    }
+    else if (session->request->method == WEBNET_DELETE)
+    {
+        LOG_D("DELETE %s", session->request->path);
+
+        unlink(session->request->path);
+
+        webnet_session_printf(session,"HTTP/1.1 204 No Content\r\n"
+                              "Server: webnet\r\n"
+                              "Content-Length: 0\r\n"
+                              "Content-Type: text/plain\r\n\r\n");
+
+        return WEBNET_MODULE_FINISHED;
+    }
+    else if (session->request->method == WEBNET_MKCOL)
+    {
+        int ret;
+
+        LOG_D("MKCOL %s", session->request->path);
+
+        ret = mkdir(session->request->path, 0x777);
+        if (ret < 0)
+        {
+            session->request->result_code = 404;
+            LOG_E("MKCOL mkdir error, path %s.", session->request->path);
+        }
+        else
+        {
+            LOG_D("MKCOL mkdir ok !");
+        }
+
+        webnet_session_printf(session,"HTTP/1.1 201 Created\r\n"
+                              "Server: webnet\r\n"
+                              "Content-Length: 0\r\n"
+                              "Content-Type: text/plain\r\n\r\n");
+
+        return WEBNET_MODULE_FINISHED;
+    }
+    else if (session->request->method == WEBNET_MOVE)
+    {
+        char *path, *full_path;
+
+        path = strstr(session->request->destination, session->request->host);
+        if (path)
+        {
+            path += strlen(session->request->host);
+
+            full_path = (char*) wn_malloc (WEBNET_PATH_MAX);
+            if (full_path)
+            {
+                webnet_session_get_physical_path(session, path, full_path);
+
+                LOG_D("Get full path, %s => %s\n", session->request->path, full_path);
+                rename(session->request->path, full_path);
+
+                wn_free(full_path);
+            }
+        }
+
+        webnet_session_printf(session,"HTTP/1.1 200 OK\r\n"
+                              "Server: webnet\r\n"
+                              "Content-Length: 0\r\n"
+                              "Content-Type: text/plain\r\n\r\n");
+
+        return WEBNET_MODULE_FINISHED;
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#include <time.h>
+
+static void print_propfind_element(struct webnet_session *session, const char *uri, struct stat *file_stat)
+{
+    char ctime_str[64];
+
+    time_t t = file_stat->st_mtime;
+
+    rt_enter_critical();
+    strftime(ctime_str, sizeof(ctime_str), "%a, %d %b %Y %H:%M:%S GMT", localtime(&t));
+    rt_exit_critical();
+    //LOG_D("strftime:  %s\n", ctime_str);
+
+    webnet_session_printf(session,
+                          propfind_element,
+                          uri, S_ISDIR(file_stat->st_mode) ? "<d:collection/>" : "",
+                          file_stat->st_size, ctime_str);
+}
+
+static int put_open (struct webnet_session* session)
+{
+    int fd;
+    if(session->request->path == RT_NULL)
+    {
+        fd = -1;
+        goto _exit;
+    }
+
+    fd = open(session->request->path, O_WRONLY | O_CREAT | O_TRUNC, 0);
+    if(fd < 0)
+    {
+        session->session_phase = WEB_PHASE_CLOSE;
+        fd = -1;
+        goto _exit;
+    }
+
+    return fd;
+
+_exit:
+    LOG_E(" %s failed  ,file:%s  ,line:%d",__FUNCTION__,__FILE__,__LINE__);
+    return (int)fd;
+}
+
+static int put_close(struct webnet_session* session)
+{
+
+    int fd;
+
+    fd = (int)webnet_put_get_userdata(session);
+    if (fd < 0) return 0;
+
+    close(fd);
+    return 0;
+}
+
+static int put_done (struct webnet_session* session)
+{
+    int put_done_h_length = 0;
+
+    static const char put_done_h[] = "HTTP/1.1 201 Created\r\n"
+                                     "Date: Mon, 07 Sep 2015 01:50:42 GMT\r\n"
+                                     "Server: %s %s\r\n"
+                                     "Location: http://%s%s \r\n"
+                                     "Content-Length: %d \r\n"
+                                     "Content-Type: text/html; charset=ISO-8859-1\r\n\r\n";
+
+    static const char put_done_t[] ="<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
+                                    "<html><head>"
+                                    "<title>";
+
+    LOG_D("put_done.");
+
+    put_done_h_length = strlen(put_done_t);
+
+    webnet_session_printf(session,put_done_h,WEBNET_THREAD_NAME,WEBNET_VERSION,inet_ntoa(session->cliaddr.sin_addr),session->request->path,put_done_h_length);
+
+    webnet_session_printf(session,put_done_t);
+
+    return 0;
+}
+
+static int put_write(struct webnet_session* session, const void* data, rt_size_t length)
+{
+    int fd;
+
+    // get fd
+    fd = (int)webnet_put_get_userdata(session);
+    if (fd < 0) return 0;
+
+    write(fd, data, length);
+
+    return length;
+}
+
+static const void* webnet_put_get_userdata(struct webnet_session* session)
+{
+    struct webnet_module_put_session *put_session;
+
+    /* get put session */
+    put_session = (struct webnet_module_put_session *)session->user_data;
+    if (put_session == RT_NULL) return RT_NULL;
+
+    return (const void*) put_session->user_data;
+}
+
+static void _webnet_module_put_close(struct webnet_session* session)
+{
+    struct webnet_module_put_session *put_session;
+
+    /* get put session */
+    put_session = (struct webnet_module_put_session *)session->user_data;
+    if (put_session == RT_NULL) return;
+    /* close file */
+    if (put_session->file_opened == 1)
+    {
+        put_session->entry->put_close(session);
+        put_session->file_opened = 0;
+    }
+
+    wn_free(put_session);
+    /* remove private data */
+    session->user_data = 0;
+    session->session_ops = RT_NULL;
+}
+
+static void _webnet_module_put_handle(struct webnet_session* session, int event)
+{
+    if(session->request->method == WEBNET_PUT)
+    {
+        int length = 0;
+        static int read_bytes = 0;
+
+        struct webnet_module_put_session *put_session;
+
+
+        put_session = (struct webnet_module_put_session *)session->user_data;
+
+        /* read stream */
+        length = webnet_session_read(session, (char *)session->buffer, sizeof(session->buffer) - 1);
+        /* connection break out */
+        if (length <= 0)
+        {
+            LOG_E(" %s failed  ,file:%s  ,line:%d\n",__FUNCTION__,__FILE__,__LINE__);
+
+            /* read stream failed (connection break out), close this session */
+            session->session_phase = WEB_PHASE_CLOSE;
+            return;
+        }
+        read_bytes = read_bytes + length;
+
+        session->buffer[length] = '\0';
+
+        /* open file */
+        if (put_session->file_opened == 0)
+        {
+            /* open file */
+            put_session->user_data = put_session->entry->put_open(session);
+            put_session->file_opened = 1;
+        }
+
+        //write data
+        if (length > 0 && session->buffer != NULL)
+        {
+            put_session->entry->put_write(session, (char*)session->buffer, length);
+        }
+
+        if (read_bytes >= (session->request->content_length))
+        {
+            put_session->entry->put_done(session);
+            _webnet_module_put_close(session);
+
+            read_bytes = 0;
+        }
+    }
+}
+
+static const struct webnet_session_ops _put_ops =
+{
+    _webnet_module_put_handle,
+    _webnet_module_put_close
+};
+
+const struct webnet_module_put_entry put_entry_put =
+{
+    put_open,
+    put_close,
+    put_write,
+    put_done
+};
+
+int webnet_module_put_method(struct webnet_session* session)
+{
+    const struct webnet_module_put_entry *entry = &put_entry_put;
+    struct webnet_module_put_session *put_session;
+
+    /* create a uploading session */
+    put_session = (struct webnet_module_put_session*) wn_malloc (sizeof (struct webnet_module_put_session));
+    if(put_session == RT_NULL)
+    {
+        LOG_E("No memory for module put session.");
+        return WEBNET_MODULE_CONTINUE;
+    }
+
+    put_session->file_opened = 0;
+    put_session->entry = entry;
+    put_session->user_data = 0;
+
+    /* add this put session into webnet session */
+    session->user_data = (rt_uint32_t) put_session;
+    /* set webnet session operations */
+    session->session_ops = &_put_ops;
+
+    return WEBNET_MODULE_FINISHED;
+}
+
+#endif /* WEBNET_USING_DAV */

+ 119 - 0
module/wn_module_index.c

@@ -0,0 +1,119 @@
+/*
+ * File      : wn_module_index.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2012-12-21     aozima       fixed version print.
+ */
+
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef RT_USING_DFS
+#include <dfs_posix.h>
+#endif
+
+#ifdef WEBNET_USING_INDEX
+
+int webnet_module_dirindex(struct webnet_session* session, int event)
+{
+    if( (session->request->method != WEBNET_GET)
+            && (session->request->method != WEBNET_POST) )
+    {
+        return WEBNET_MODULE_CONTINUE;
+    }
+
+    if (event == WEBNET_EVENT_URI_POST)
+    {
+        DIR *dir;
+        struct webnet_request *request;
+        static const char* header = "<html><head><title>Index of %s</title></head><body bgcolor=\"white\"><h1>Index of %s</h1><hr><pre>";
+        static const char* foot = "</pre><hr>WebNet/%s (RT-Thread)</body></html>";
+
+        RT_ASSERT(session != RT_NULL);
+        request = session->request;
+        RT_ASSERT(request != RT_NULL);
+
+        dir = opendir(request->path);
+        if (dir != RT_NULL)
+        {
+            struct stat s;
+            struct dirent* dirent;
+            const char* sub_path;
+            char *fullpath;
+            char *delim;
+
+            dirent = RT_NULL;
+            fullpath = wn_malloc (WEBNET_PATH_MAX);
+            if (fullpath == RT_NULL)
+            {
+                request->result_code = 500;
+                return WEBNET_MODULE_FINISHED;
+            }
+
+            webnet_session_set_header(session, "text/html", 200, "OK", -1);
+            /* get sub path */
+            sub_path = request->path + strlen(webnet_get_root());
+            delim = strrchr(sub_path, '/');
+            rt_snprintf(fullpath, delim - sub_path + 1, "%s", sub_path);
+            webnet_session_printf(session, header, sub_path, sub_path);
+            /* display parent directory */
+            webnet_session_printf(session, "<a href=\"../\">..</a>\n");
+
+            /* list directory */
+            do
+            {
+                dirent = readdir(dir);
+                if (dirent == RT_NULL) break;
+
+                rt_memset(&s, 0, sizeof(struct stat));
+
+                /* build full path for each file */
+                rt_sprintf(fullpath, "%s/%s", request->path, dirent->d_name);
+                str_normalize_path(fullpath);
+
+                stat(fullpath, &s);
+                rt_sprintf(fullpath, "%s/%s", sub_path, dirent->d_name);
+                if ( s.st_mode & S_IFDIR )
+                {
+                    webnet_session_printf(session, "<a href=\"%s/\">%s/</a>\n", fullpath, dirent->d_name);
+                }
+                else
+                {
+                    webnet_session_printf(session, "<a href=\"%s\">%s</a>\t\t\t\t\t%d\n", fullpath, dirent->d_name, s.st_size);
+                }
+            }
+            while (dirent != RT_NULL);
+
+            closedir(dir);
+            wn_free(fullpath);
+
+            /* set foot */
+            webnet_session_printf(session, foot, WEBNET_VERSION);
+
+            return WEBNET_MODULE_FINISHED;
+        }
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#endif /* WEBNET_USING_INDEX */
+

+ 112 - 0
module/wn_module_log.c

@@ -0,0 +1,112 @@
+/*
+ * File      : wn_module_log.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ */
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef RT_USING_SAL
+#include <arpa/inet.h>
+#else
+#include <lwip/inet.h>
+#endif /* RT_USING_SAL */
+
+#define DBG_ENABLE
+#define DBG_COLOR
+#define DBG_SECTION_NAME    "wn.log"
+#define DBG_LEVEL           DBG_LOG
+#include <rtdbg.h>
+
+#ifdef WEBNET_USING_LOG
+
+int webnet_module_log(struct webnet_session* session, int event)
+{
+    struct webnet_request* request;
+
+    if (session != RT_NULL)
+    {
+        request = session->request;
+    }
+    else
+    {
+        request = RT_NULL;
+    }
+
+    if (event == WEBNET_EVENT_INIT)
+    {
+        LOG_D("server initialize success.");
+    }
+    else if (event == WEBNET_EVENT_URI_PHYSICAL)
+    {
+        rt_uint32_t index;
+        LOG_RAW("\n");
+        LOG_D("  new client: %s:%u",
+                   inet_ntoa(session->cliaddr.sin_addr),
+                   ntohs(session->cliaddr.sin_port));
+
+        switch (request->method)
+        {
+        case WEBNET_GET:
+            LOG_D("      method: GET");
+            break;
+        case WEBNET_PUT:
+            LOG_D("      method: PUT");
+            break;
+        case WEBNET_POST:
+            LOG_D("      method: POST");
+            break;
+        case WEBNET_HEADER:
+            LOG_D("      method: HEADER");
+            break;
+        case WEBNET_SUBSCRIBE:
+            LOG_D("      method: SUBSCRIBE");
+            break;
+        case WEBNET_UNSUBSCRIBE:
+            LOG_D("      method: UNSUBSCRIBE");
+            break;
+        default:
+            break;
+        }
+        LOG_D("     request: %s", request->path);
+        for (index = 0; index < request->query_counter; index ++)
+        {
+            LOG_D("    query[%d]: %s => %s", index,
+                       request->query_items[index].name,
+                       request->query_items[index].value);
+        }
+    }
+    else if (event == WEBNET_EVENT_URI_POST)
+    {
+        LOG_D("physical url: %s", request->path);
+        LOG_D("   mime type: %s", mime_get_type(request->path));
+    }
+    else if (event == WEBNET_EVENT_RSP_HEADER)
+    {
+    }
+    else if (event == WEBNET_EVENT_RSP_FILE)
+    {
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#endif /* WEBNET_USING_LOG */

+ 181 - 0
module/wn_module_lua.c

@@ -0,0 +1,181 @@
+/*
+ * File      : wn_module_lua.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2013-11-23     aozima       redirect lua print.
+ */
+
+#include <string.h>
+
+#include <webnet.h>
+#include <wn_module.h>
+
+#ifdef RT_USING_DFS
+#include <dfs_posix.h>
+#endif
+
+#if defined(WEBNET_USING_LUA) && defined(RT_USING_LUA)
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+static struct webnet_session* lua_session;
+
+static int print(lua_State *L)
+{
+    int n = lua_gettop(L);
+    int i;
+
+    if(lua_session == RT_NULL)
+        return 0;
+
+    for (i=1; i<=n; i++)
+    {
+        if (lua_isstring(L,i))
+            webnet_session_printf(lua_session, "%s", lua_tostring(L,i));
+        else if (lua_isnumber(L, i))
+            webnet_session_printf(lua_session, "%d", lua_tointeger(L,i));
+        else if (lua_isnil(L, i))
+            webnet_session_printf(lua_session, "%s", "nil");
+        else if (lua_isboolean(L, i))
+            webnet_session_printf(lua_session, "%s", lua_toboolean(L,i) ? "true" : "false");
+        else
+            webnet_session_printf(lua_session, "%s:%p", luaL_typename(L,i), lua_topointer(L, i));
+    }
+
+    return 0;
+}
+
+static void _webnet_lua_dofile(struct webnet_session* session, const char * path)
+{
+    int ret = 0;
+
+    lua_State *L = lua_open();
+    if(L == RT_NULL)
+        return;
+
+    lua_session = session;
+
+//    luaopen_base(L);
+//    luaopen_table(L);
+//    luaopen_io(L);
+//    luaopen_os(L);
+//    luaopen_string(L);
+//    luaopen_math(L);
+//    luaopen_debug(L);
+//    luaopen_package(L);
+
+    luaL_openlibs(L);
+
+    lua_register(L, "print", print);
+
+    ret = luaL_loadfile(L, path);
+    if ( ret != 0 ) goto _exception;
+
+    ret = lua_pcall(L,0, LUA_MULTRET,0) ;
+    if ( ret != 0 ) goto _exception;
+
+_exception:
+    if(ret != 0)
+    {
+        const char* fmt = "<html><head><title>%d %s</title></head><body>%d %s</body></html>\r\n";
+        struct webnet_request* request;
+
+        const char* title = "Internal Server Error";
+        int code = 500;
+
+
+        RT_ASSERT(session != RT_NULL);
+        request = session->request;
+        RT_ASSERT(request != RT_NULL);
+
+        webnet_session_set_header(session, "text/html", request->result_code, title, -1);
+        webnet_session_printf(session, fmt, request->result_code, title, code, lua_tostring(L,-1));
+
+        request->result_code = 200; /* set code 200 to end the session. */
+    }
+
+    lua_close(L);
+    lua_session = RT_NULL;
+    return;
+}
+
+int webnet_module_lua(struct webnet_session* session, int event)
+{
+    struct webnet_request* request;
+
+    RT_ASSERT(session != RT_NULL);
+    request = session->request;
+    RT_ASSERT(request != RT_NULL);
+
+    if (event == WEBNET_EVENT_URI_POST)
+    {
+        int fd;
+
+        /* check whether a lua script */
+        if ((strstr(request->path, ".lua") != RT_NULL) ||
+                (strstr(request->path, ".LUA") != RT_NULL))
+        {
+            /* try to open this file */
+            fd = open(request->path, O_RDONLY, 0);
+            if ( fd >= 0)
+            {
+                close(fd);
+                _webnet_lua_dofile(session, request->path);
+
+                return WEBNET_MODULE_FINISHED;
+            }
+            else
+            {
+                /* no this file */
+                request->result_code = 404;
+                return WEBNET_MODULE_FINISHED;
+            }
+        }
+        else
+        {
+            /* try index.lua */
+            char *lua_filename;
+
+            lua_filename = (char*) wn_malloc (WEBNET_PATH_MAX);
+            if (lua_filename != RT_NULL)
+            {
+                rt_snprintf(lua_filename, WEBNET_PATH_MAX, "%s/index.lua", request->path);
+                fd = open(lua_filename, O_RDONLY, 0);
+
+                if (fd >= 0)
+                {
+                    close(fd);
+                    _webnet_lua_dofile(session, lua_filename);
+                    wn_free(lua_filename);
+
+                    return WEBNET_MODULE_FINISHED;
+                }
+
+                wn_free(lua_filename);
+            }
+        }
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#endif /* defined(WEBNET_USING_LUA) && defined(RT_USING_LUA) */

+ 257 - 0
module/wn_module_ssi.c

@@ -0,0 +1,257 @@
+/*
+ * File      : wn_module_ssi.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2012-06-25     Bernard      the first version
+ */
+#include <webnet.h>
+#include <wn_session.h>
+#include <wn_module.h>
+
+#ifdef RT_USING_DFS
+#include <dfs_posix.h>
+#endif
+
+#if defined(WEBNET_USING_SSI)
+
+#define SSI_INCLUDE_STRING	"<!--#include "
+#define SSI_EXEC_STRING		"<!--#exec "
+#define SSI_VIRTUAL_STRING	"virtual=\""
+#define SSI_FILE_STRING		"file=\""
+#define SSI_CGI_STRING		"cgi=\""
+#define SSI_END_STRING		"\" -->"
+
+static void _webnet_ssi_sendfile(struct webnet_session* session, const char* filename)
+{
+    int fd;
+    int file_length;
+    rt_size_t size, readbytes;
+
+    fd = open(filename, O_RDONLY, 0);
+    if (fd < 0) return; /* open file failed */
+
+    /* get file size */
+    file_length = lseek(fd, 0, SEEK_END);
+    /* seek to beginning of file */
+    lseek(fd, 0, SEEK_SET);
+    if (file_length <= 0)
+    {
+        close(fd);
+        return ;
+    }
+    while (file_length)
+    {
+        if (file_length > sizeof(session->buffer))
+            size = (rt_size_t) sizeof(session->buffer);
+        else
+            size = file_length;
+
+        readbytes = read(fd, session->buffer, size);
+        if (readbytes <= 0)
+            /* no more data */
+            break;
+
+        if (webnet_session_write(session, session->buffer, readbytes) == 0)
+            break;
+
+        file_length -= (long) readbytes;
+    }
+
+    /* close file */
+    close(fd);
+}
+
+static void _webnet_ssi_dofile(struct webnet_session* session, int fd)
+{
+    char *ssi_begin, *ssi_end;
+    char *offset, *end;
+    char *buffer;
+    char *path;
+    rt_uint32_t length;
+
+    ssi_begin = ssi_end = RT_NULL;
+    offset = end = RT_NULL;
+    buffer = path = RT_NULL;
+
+    /* get file length */
+    length = lseek(fd, 0, SEEK_END);
+    lseek(fd, 0, SEEK_SET);
+
+    /* allocate ssi include file path */
+    path = (char*) wn_malloc(WEBNET_PATH_MAX);
+    /* allocate read buffer */
+    buffer = (char*) wn_malloc (length);
+    if (path == RT_NULL || buffer == RT_NULL)
+    {
+        session->request->result_code = 500;
+        goto __exit;
+    }
+
+    /* write page header */
+    webnet_session_set_header(session, "text/html", 200, "OK", -1);
+    /* read file to buffer */
+    if (read(fd, buffer, length) != length) /* read failed */
+    {
+        session->request->result_code = 500;
+        close(fd);
+        goto __exit;
+    }
+    /* close file */
+    close(fd);
+
+    offset = buffer;
+    end = buffer + length;
+    while (offset < end)
+    {
+        /* get beginning of ssi */
+        ssi_begin = strstr(offset, SSI_INCLUDE_STRING);
+        if (ssi_begin == RT_NULL)
+        {
+            /* write content directly */
+            webnet_session_write(session, (const rt_uint8_t*)offset, end - offset);
+            break;
+        }
+
+        /* get end of ssi */
+        ssi_end = strstr(ssi_begin, "\" -->");
+        if (ssi_end == RT_NULL)
+        {
+            /* write content directly */
+            webnet_session_write(session, (const rt_uint8_t*)offset, end - offset);
+            break;
+        }
+        else
+        {
+            char *include_begin, *include_end;
+            char *filename;
+
+            /* write content */
+            webnet_session_write(session, (const rt_uint8_t*)offset, ssi_begin - offset);
+
+            offset = ssi_begin + sizeof(SSI_INCLUDE_STRING) - 1;
+            include_begin = strstr(ssi_begin, SSI_VIRTUAL_STRING);
+            if (include_begin != RT_NULL)
+            {
+                filename = include_begin + sizeof(SSI_VIRTUAL_STRING) - 1;
+                include_end = strstr(filename, "\"");
+                *include_end = '\0';
+
+                if (webnet_session_get_physical_path(session, filename, path) == 0)
+                {
+                    _webnet_ssi_sendfile(session, path);
+                }
+            }
+            else
+            {
+                include_begin = strstr(ssi_begin, SSI_FILE_STRING);
+                if (include_begin != RT_NULL)
+                {
+                    filename = include_begin + sizeof(SSI_FILE_STRING) - 1;
+                    include_end = strstr(filename, "\"");
+                    *include_end = '\0';
+
+                    _webnet_ssi_sendfile(session, filename);
+                }
+            }
+            offset = ssi_end + sizeof(SSI_END_STRING) - 1;
+        }
+    }
+
+    /* exit and release buffer buffer */
+__exit:
+    if (path != RT_NULL) wn_free(path);
+    if (buffer != RT_NULL) wn_free(buffer);
+}
+
+static const char* ssi_extension[] =
+{
+    ".shtm",
+    ".SHTM",
+    ".shtml",
+    ".SHTML",
+    ".stm",
+    ".STM",
+    RT_NULL
+};
+
+int webnet_module_ssi(struct webnet_session* session, int event)
+{
+    struct webnet_request* request;
+
+    /* check parameter */
+    RT_ASSERT(session != RT_NULL);
+    request = session->request;
+    RT_ASSERT(request != RT_NULL);
+
+    if (event == WEBNET_EVENT_URI_POST)
+    {
+        int fd;
+        int index;
+
+        /* check whether a ssi file */
+        index = 0;
+        while (ssi_extension[index] != RT_NULL)
+        {
+            if (strstr(request->path, ssi_extension[index]) != RT_NULL)
+            {
+                /* try to open this file */
+                fd = open(request->path, O_RDONLY, 0);
+                if ( fd >= 0)
+                {
+                    _webnet_ssi_dofile(session, fd);
+                    close(fd);
+                    return WEBNET_MODULE_FINISHED;
+                }
+                else
+                {
+                    /* no this file */
+                    request->result_code = 404;
+                    return WEBNET_MODULE_FINISHED;
+                }
+            }
+
+            index ++;
+        }
+
+        /* no this file, try index.shtm */
+        {
+            char *ssi_filename;
+
+            ssi_filename = (char*) wn_malloc (WEBNET_PATH_MAX);
+            if (ssi_filename != RT_NULL)
+            {
+                rt_snprintf(ssi_filename, WEBNET_PATH_MAX, "%s/index.shtm", request->path);
+                fd = open(ssi_filename, O_RDONLY, 0);
+
+                if (fd >= 0)
+                {
+                    wn_free(ssi_filename);
+                    _webnet_ssi_dofile(session, fd);
+                    close(fd);
+                    return WEBNET_MODULE_FINISHED;
+                }
+            }
+            wn_free(ssi_filename);
+        }
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+#endif

+ 778 - 0
module/wn_module_upload.c

@@ -0,0 +1,778 @@
+/*
+ * File      : wn_module_upload.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2012-06-25     Bernard      the first version
+ */
+
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef RT_USING_DFS
+#include <dfs_posix.h>
+#endif
+
+#if defined(WEBNET_USING_UPLOAD)
+
+#define MULTIPART_FORM_DATA_STRING              "multipart/form-data"
+#define BOUNDARY_STRING                         "boundary="
+#define CONTENT_DISPOSITION_STRING              "Content-Disposition:"
+#define CONTENT_TYPE_STRING                     "Content-Type:"
+#define CONTENT_RANGE_STRING                    "Content-Range:"
+#define FORM_DATA_STRING                        "form-data"
+#define FILENAME_STRING                         "filename=\""
+#define FIELDNAME_STRING                        "name=\""
+
+#define FIRST_BOUNDARY                          2, upload_session->boundary, "\r\n"
+#define NORMAL_BOUNDARY                         3, "\r\n", upload_session->boundary, "\r\n"
+#define COMMON_BOUNDARY                         2, "\r\n", upload_session->boundary
+#define LAST_BOUNDARY                           3, "\r\n", upload_session->boundary, "--\r\n"
+#define FIRST_BOUNDARY_SIZE                     (strlen(upload_session->boundary) + 2)
+#define NORMAL_BOUNDARY_SIZE                    (strlen(upload_session->boundary) + 4)
+#define COMMON_BOUNDARY_SIZE                    (strlen(upload_session->boundary) + 2)
+#define LAST_BOUNDARY_SIZE                      (strlen(upload_session->boundary) + 6)
+
+struct webnet_upload_name_entry
+{
+    char *name;
+    char *value;
+};
+static const struct webnet_module_upload_entry **_upload_entries = RT_NULL;
+static rt_uint16_t _upload_entries_count = 0;
+
+struct webnet_module_upload_session
+{
+    char* boundary;
+
+    char* filename;
+    char* content_type;
+
+    struct webnet_upload_name_entry* name_entries;
+    rt_uint16_t name_entries_count;
+    rt_uint16_t file_opened;
+
+    /* upload entry */
+    const struct webnet_module_upload_entry* entry;
+
+    /* user data */
+    rt_uint32_t user_data;
+};
+
+static int str_begin_with_strs(const char* str, int num, ...)
+{
+    char *match;
+    va_list args;
+    int index, result;
+
+    result = 1;
+    va_start(args,num);
+    for (index = 0; index < num; index ++)
+    {
+        match = va_arg(args, char*);
+        if (strncasecmp(str, match, strlen(match)) != 0)
+        {
+            result = 0;
+            break;
+        }
+
+        str += strlen(match);
+    }
+    va_end(args);
+
+    return result;
+}
+
+/* Search string on binary data */
+static char *memstr(const char *haystack, size_t length, const char *needle)
+{
+    size_t nl=strlen(needle);
+    size_t hl=length;
+    size_t i;
+
+    if (!nl) goto __found;
+    if (nl > length) return 0;
+
+    for (i=hl-nl+1; i; --i)
+    {
+        if (*haystack==*needle && !memcmp(haystack,needle,nl))
+__found:
+            return (char*)haystack;
+        ++haystack;
+    }
+
+    return 0;
+}
+
+/* Search string array on binary data */
+static char* memstrs(const char* str, size_t length, int num, ...)
+{
+    char *substr;
+    char *index_str, *ptr;
+
+    va_list args;
+    int index;
+    int total_size;
+
+    /* get the match total string length */
+    va_start(args, num);
+    total_size = 0;
+    for (index = 0; index < num; index ++)
+    {
+        index_str = va_arg(args, char*);
+        total_size += strlen(index_str);
+    }
+    va_end(args);
+
+    substr = wn_malloc(total_size + 1);
+    ptr = substr;
+    va_start(args, num);
+    for (index = 0; index < num; index++)
+    {
+        index_str = va_arg(args, char*);
+        memcpy(ptr, index_str, strlen(index_str));
+        ptr += strlen(index_str);
+    }
+    substr[total_size] = '\0';
+    va_end(args);
+
+    /* find in binary data */
+    ptr = memstr(str, length, substr);
+    wn_free(substr);
+
+    return ptr;
+}
+
+#ifndef __GNUC__
+static void* memrchr(const void *s, int c, size_t n)
+{
+    register const char* t=s;
+    register const char* last=0;
+    int i;
+
+    t = t + n;
+    for (i=n; i; --i)
+    {
+        if (*t==c)
+        {
+            last=t;
+            break;
+        }
+        t--;
+    }
+    return (void*)last; /* man, what an utterly b0rken prototype */
+}
+#endif
+
+static char* _webnet_module_upload_parse_header(struct webnet_session* session, char* buffer, rt_size_t length)
+{
+    char *ptr, *ch, *end_ptr;
+    struct webnet_module_upload_session *upload_session;
+    char *name = RT_NULL, *filename = RT_NULL, *content_type = RT_NULL;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+
+    ptr = buffer;
+    end_ptr = buffer + length;
+    /* begin with last boundary */
+    if (str_begin_with_strs(ptr, LAST_BOUNDARY))
+    {
+        ptr += LAST_BOUNDARY_SIZE;
+        return ptr;
+    }
+    /* begin with normal boundary */
+    if (str_begin_with_strs(ptr, NORMAL_BOUNDARY))
+        ptr += NORMAL_BOUNDARY_SIZE;
+    else if (str_begin_with_strs(ptr, FIRST_BOUNDARY))
+        ptr += FIRST_BOUNDARY_SIZE;
+    if (ptr == buffer) return RT_NULL; /* not begin with boundary */
+
+    if (upload_session->filename != RT_NULL &&
+            upload_session->content_type != RT_NULL)
+    {
+        /* end of file name section */
+        wn_free(upload_session->filename);
+        wn_free(upload_session->content_type);
+
+        upload_session->filename = RT_NULL;
+        upload_session->content_type = RT_NULL;
+    }
+
+    while ((rt_uint32_t)ptr - (rt_uint32_t)buffer < length)
+    {
+        /* handle Content-Disposition: */
+        if (str_begin_with(ptr, CONTENT_DISPOSITION_STRING))
+        {
+            ptr += sizeof(CONTENT_DISPOSITION_STRING) - 1;
+            while (*ptr == ' ') ptr ++;
+
+            /* form-data */
+            if (str_begin_with(ptr, FORM_DATA_STRING))
+            {
+                ptr += sizeof(FORM_DATA_STRING) - 1;
+                while (*ptr == ' ' || *ptr == ';') ptr ++;
+
+                /* get name="str" */
+                if (str_begin_with(ptr, FIELDNAME_STRING))
+                {
+                    ptr += sizeof(FIELDNAME_STRING) - 1;
+                    name = ptr;
+
+                    ch = memchr(ptr, '"', buffer - ptr);
+                    if (ch != RT_NULL)
+                    {
+                        *ch = '\0';
+                        ch ++;
+                        while (*ch == ' ' || *ch ==';') ch ++;
+
+                        ptr = ch;
+                    }
+                }
+
+                /* get filename="str" */
+                if (str_begin_with(ptr, FILENAME_STRING))
+                {
+                    ptr += sizeof(FILENAME_STRING) - 1;
+                    filename = ptr;
+
+                    ch = memchr(ptr, '"', buffer - ptr);
+                    if (ch != RT_NULL)
+                    {
+                        *ch = '\0';
+                        ch ++;
+                        ptr = ch;
+                    }
+                }
+            }
+        }
+        /* handle Content-Type */
+        else if (str_begin_with(ptr, CONTENT_TYPE_STRING))
+        {
+            ptr += sizeof(CONTENT_TYPE_STRING) - 1;
+            while (*ptr == ' ') ptr ++;
+
+            content_type = ptr;
+        }
+        /* handle Content-Range: */
+        else if (str_begin_with(ptr, CONTENT_RANGE_STRING))
+        {
+            ptr += sizeof(CONTENT_RANGE_STRING) - 1;
+            while (*ptr == ' ') ptr ++;
+        }
+        /* whether end of header */
+        else if (str_begin_with(ptr, "\r\n"))
+        {
+            ptr += 2;
+
+            if (upload_session->filename != RT_NULL)
+            {
+                wn_free(upload_session->filename);
+                upload_session->filename = RT_NULL;
+            }
+            if (upload_session->content_type != RT_NULL)
+            {
+                wn_free(upload_session->content_type);
+                upload_session->content_type = RT_NULL;
+            }
+
+            if (filename != RT_NULL)
+            {
+                upload_session->filename = wn_strdup(filename);
+            }
+            if (content_type != RT_NULL)
+            {
+                upload_session->content_type = wn_strdup(content_type);
+            }
+            if (name != RT_NULL)
+            {
+                /* add a name entry in the upload session */
+                if (upload_session->name_entries == RT_NULL)
+                {
+                    upload_session->name_entries_count = 1;
+                    upload_session->name_entries = (struct webnet_upload_name_entry*)
+                                                   wn_malloc(sizeof(struct webnet_upload_name_entry));
+                }
+                else
+                {
+                    upload_session->name_entries_count += 1;
+                    upload_session->name_entries = (struct webnet_upload_name_entry*)
+                                                   wn_realloc(upload_session->name_entries,
+                                                           sizeof(struct webnet_upload_name_entry) * upload_session->name_entries_count);
+                }
+
+                upload_session->name_entries[upload_session->name_entries_count - 1].name = wn_strdup(name);
+                upload_session->name_entries[upload_session->name_entries_count - 1].value = RT_NULL;
+            }
+
+            return ptr;
+        }
+
+        /* skip this request header line */
+        ch = memstr(ptr, end_ptr - ptr, "\r\n");
+        *ch = '\0';
+        ch ++;
+        *ch = '\0';
+        ch ++;
+        ptr = ch;
+    }
+
+    return ptr;
+}
+
+static char* _next_possible_boundary(struct webnet_session* session, char* buffer, rt_size_t length)
+{
+    char *ptr;
+    struct webnet_module_upload_session *upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+
+    /* try the beginning */
+    if (str_begin_with_strs(buffer, COMMON_BOUNDARY)) return buffer;
+    /* search in all the string */
+    ptr = memstrs(buffer, length, COMMON_BOUNDARY);
+    if (ptr != RT_NULL) return ptr;
+
+    /* search from the end of string */
+    ptr = memrchr(buffer, '\r', length);
+    if (ptr == RT_NULL ) return RT_NULL;
+    if (ptr - buffer == length - 1) return ptr; /* only \r */
+    if (*(ptr + 1) == '\n' && ptr - buffer == length - 2) return RT_NULL; /* only \r\n */
+
+    if (memcmp(ptr + 2, upload_session->boundary, length - (ptr - buffer + 2)) == 0) return ptr;
+
+    return RT_NULL;
+}
+
+static void _handle_section(struct webnet_session* session, char* buffer, rt_size_t length)
+{
+    char *ptr;
+    struct webnet_module_upload_session *upload_session;
+
+#define name_entry  \
+    (upload_session->name_entries[upload_session->name_entries_count - 1])
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    if (upload_session->filename != RT_NULL &&
+            upload_session->content_type != RT_NULL&&
+            length != 0)
+    {
+        upload_session->entry->upload_write(session, buffer, length);
+    }
+    else
+    {
+        /* get name value */
+        ptr = memstr(buffer, length, "\r\n");
+        if (ptr != RT_NULL)
+        {
+            int value_size = ptr - buffer + 1;
+            name_entry.value = wn_malloc(value_size);
+            memcpy(name_entry.value, buffer, value_size - 1);
+            name_entry.value[value_size - 1] = '\0';
+        }
+    }
+}
+
+static void _webnet_module_upload_handle(struct webnet_session* session, int event)
+{
+    int length;
+    char *ptr, *end_ptr;
+    char *upload_buffer;
+    rt_uint32_t chunk_size;
+    struct webnet_module_upload_session *upload_session;
+
+    if (event != WEBNET_EVENT_READ) return;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    upload_buffer = (char*)session->buffer;
+
+    /* read stream */
+    if (memstrs((const char *)session->buffer, session->buffer_offset, LAST_BOUNDARY))
+    {
+        length = session->buffer_offset;
+    }
+    else
+    {
+        if (session->buffer_offset != 0)
+        {
+            length = webnet_session_read(session,
+                (char*)&session->buffer[session->buffer_offset],
+                sizeof(session->buffer) - session->buffer_offset - 1);
+            if (length > 0)
+            {
+                length += session->buffer_offset;
+            }
+            else
+            {
+                length = session->buffer_offset;
+            }
+        }
+        else
+        {
+            length = webnet_session_read(session, (char *)session->buffer, sizeof(session->buffer) - 1);
+        }
+    }
+
+    /* connection break out */
+    if (length <= 0)
+    {
+        /* read stream failed (connection break out), close this session */
+        session->session_phase = WEB_PHASE_CLOSE;
+        return;
+    }
+    end_ptr = (char*)(session->buffer + length);
+
+    session->buffer[length] = '\0';
+    while (upload_buffer < end_ptr)
+    {
+        if (str_begin_with_strs(upload_buffer, LAST_BOUNDARY))
+        {
+            /* upload done */
+            upload_session->entry->upload_done(session);
+            session->session_phase = WEB_PHASE_CLOSE;
+            return;
+        }
+
+        /* read more data */
+        if (end_ptr - upload_buffer < sizeof(session->buffer)/3 &&
+                memstrs(upload_buffer, end_ptr - upload_buffer, LAST_BOUNDARY) == RT_NULL)
+        {
+            /* read more data */
+            rt_memmove(session->buffer, upload_buffer, end_ptr - upload_buffer);
+            session->buffer_offset = end_ptr - upload_buffer;
+            return ;
+        }
+
+        ptr = _webnet_module_upload_parse_header(session, upload_buffer,
+                (rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
+        if (ptr == RT_NULL)
+        {
+            /* not begin with a boundary */
+            ptr = _next_possible_boundary(session, upload_buffer,
+                                          (rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
+            if (ptr == RT_NULL)
+            {
+                /* all are the data section */
+                _handle_section(session, upload_buffer, (rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
+                upload_buffer = end_ptr;
+            }
+            else
+            {
+                chunk_size = (rt_uint32_t)ptr - (rt_uint32_t)upload_buffer;
+                _handle_section(session, upload_buffer, chunk_size);
+                upload_buffer += chunk_size;
+            }
+        }
+        else
+        {
+            if (upload_session->filename != RT_NULL &&
+                    upload_session->content_type != RT_NULL)
+            {
+                if (upload_session->file_opened == 0)
+                {
+                    /* open file */
+                    upload_session->user_data = upload_session->entry->upload_open(session);
+                    upload_session->file_opened = 1;
+                }
+            }
+            else
+            {
+                if (upload_session->file_opened == 1)
+                {
+                    /* close file */
+                    upload_session->entry->upload_close(session);
+                    upload_session->user_data = 0;
+                    upload_session->file_opened = 0;
+                }
+            }
+
+            upload_buffer = ptr;
+            ptr = _next_possible_boundary(session, upload_buffer,
+                                          (rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
+            if (ptr == RT_NULL)
+            {
+                /* all are the data section */
+                _handle_section(session, upload_buffer, (rt_uint32_t)end_ptr - (rt_uint32_t)upload_buffer);
+                upload_buffer = end_ptr;
+            }
+            else
+            {
+                chunk_size = (rt_uint32_t)ptr - (rt_uint32_t)upload_buffer;
+                _handle_section(session, upload_buffer, chunk_size);
+                upload_buffer += chunk_size;
+            }
+        }
+    }
+
+    session->buffer_offset = 0;
+}
+
+static void _webnet_module_upload_close(struct webnet_session* session)
+{
+    struct webnet_module_upload_session *upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    if (upload_session == RT_NULL) return;
+
+    /* close file */
+    if (upload_session->file_opened == 1)
+    {
+        upload_session->entry->upload_close(session);
+        upload_session->file_opened = 0;
+    }
+
+    /* free session */
+    if (upload_session->filename != RT_NULL)
+        wn_free(upload_session->filename);
+    if (upload_session->content_type != RT_NULL)
+        wn_free(upload_session->content_type);
+    if (upload_session->boundary != RT_NULL)
+        wn_free(upload_session->boundary);
+    if (upload_session->entry != RT_NULL)
+    {
+        rt_uint32_t index;
+        for (index = 0; index < upload_session->name_entries_count; index ++)
+        {
+            if (upload_session->name_entries[index].value != RT_NULL)
+                wn_free(upload_session->name_entries[index].value);
+            if (upload_session->name_entries[index].name != RT_NULL)
+                wn_free(upload_session->name_entries[index].name);
+        }
+        wn_free(upload_session->name_entries);
+        upload_session->name_entries = RT_NULL;
+    }
+    wn_free(upload_session);
+
+    /* remove private data */
+    session->user_data = 0;
+    session->session_ops = RT_NULL;
+    session->session_phase = WEB_PHASE_CLOSE;
+}
+
+static const struct webnet_session_ops _upload_ops =
+{
+    _webnet_module_upload_handle,
+    _webnet_module_upload_close
+};
+
+int webnet_module_upload_open(struct webnet_session* session)
+{
+    char* boundary;
+    rt_uint32_t index, length;
+    const struct webnet_module_upload_entry *entry = RT_NULL;
+    struct webnet_module_upload_session *upload_session;
+
+    if (session->request->method != WEBNET_POST)
+        return WEBNET_MODULE_CONTINUE;
+
+    /* get upload entry */
+    for (index = 0; index < _upload_entries_count; index ++)
+    {
+        if (str_begin_with(session->request->path, _upload_entries[index]->url))
+        {
+            length = rt_strlen(_upload_entries[index]->url);
+            if (session->request->path[length] == '\0' ||
+                    session->request->path[length] == '?')
+            {
+                /* found entry */
+                entry = _upload_entries[index];
+                break;
+            }
+        }
+    }
+    if (entry == RT_NULL) /* no this entry */
+        return WEBNET_MODULE_CONTINUE;
+
+    /* create a uploading session */
+    upload_session = (struct webnet_module_upload_session*) wn_malloc (
+                         sizeof (struct webnet_module_upload_session));
+    if (upload_session == RT_NULL) return 0; /* no memory */
+
+    /* get boundary */
+    boundary = strstr(session->request->content_type, BOUNDARY_STRING);
+    if (boundary != RT_NULL)
+    {
+        int boundary_size;
+
+        /* make boundary */
+        boundary_size = strlen(boundary + sizeof(BOUNDARY_STRING) - 1) + 3;
+        upload_session->boundary = wn_malloc(boundary_size);
+        rt_sprintf(upload_session->boundary, "--%s", boundary + sizeof(BOUNDARY_STRING) - 1);
+    }
+    upload_session->filename = RT_NULL;
+    upload_session->content_type = RT_NULL;
+    upload_session->name_entries = RT_NULL;
+    upload_session->name_entries_count = 0;
+    upload_session->file_opened = 0;
+    upload_session->entry = entry;
+    upload_session->user_data = 0;
+
+    /* add this upload session into webnet session */
+    session->user_data = (rt_uint32_t) upload_session;
+    /* set webnet session operations */
+    session->session_ops = &_upload_ops;
+
+    session->session_ops->session_handle(session, WEBNET_EVENT_READ);
+
+    return WEBNET_MODULE_FINISHED;
+}
+
+int webnet_module_upload(struct webnet_session* session, int event)
+{
+    if (event == WEBNET_EVENT_URI_PHYSICAL)
+    {
+        return webnet_module_upload_open(session);
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+/* webnet upload module APIs */
+void webnet_upload_add(const struct webnet_module_upload_entry* entry)
+{
+    if (_upload_entries == RT_NULL)
+    {
+        _upload_entries_count = 1;
+
+        /* first entries, malloc upload entry */
+        _upload_entries = (const struct webnet_module_upload_entry**)
+                          wn_malloc (sizeof(struct webnet_module_upload_entry));
+    }
+    else
+    {
+        _upload_entries_count += 1;
+        _upload_entries = (const struct webnet_module_upload_entry**) wn_realloc (_upload_entries,
+                          sizeof(void*) * _upload_entries_count);
+    }
+    RT_ASSERT(_upload_entries != RT_NULL);
+
+    _upload_entries[_upload_entries_count - 1] = entry;
+}
+RTM_EXPORT(webnet_upload_add);
+
+const char* webnet_upload_get_filename(struct webnet_session* session)
+{
+    struct webnet_module_upload_session *upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    if (upload_session == RT_NULL) return RT_NULL;
+
+    return upload_session->filename;
+}
+RTM_EXPORT(webnet_upload_get_filename);
+
+const char* webnet_upload_get_content_type(struct webnet_session* session)
+{
+    struct webnet_module_upload_session *upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    if (upload_session == RT_NULL) return RT_NULL;
+
+    return upload_session->content_type;
+}
+RTM_EXPORT(webnet_upload_get_content_type);
+
+const char* webnet_upload_get_nameentry(struct webnet_session* session, const char* name)
+{
+    rt_uint32_t index;
+    struct webnet_module_upload_session *upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    if (upload_session == RT_NULL) return RT_NULL;
+
+    for (index = 0; index < upload_session->name_entries_count; index ++)
+    {
+        if (strcasecmp(upload_session->name_entries[index].name, name) == 0)
+        {
+            return upload_session->name_entries[index].value;
+        }
+    }
+
+    return RT_NULL;
+}
+RTM_EXPORT(webnet_upload_get_nameentry);
+
+const void* webnet_upload_get_userdata(struct webnet_session* session)
+{
+    struct webnet_module_upload_session *upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    if (upload_session == RT_NULL) return RT_NULL;
+
+    return (const void*) upload_session->user_data;
+}
+RTM_EXPORT(webnet_upload_get_userdata);
+
+/* ---- upload file interface ---- */
+int webnet_upload_file_open(struct webnet_session* session)
+{
+    struct webnet_module_upload_session* upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    if (upload_session->filename != RT_NULL)
+    {
+        /* open file */
+        upload_session->user_data = open(upload_session->filename, O_WRONLY, 0);
+        return upload_session->user_data;
+    }
+
+    return -1;
+}
+
+int webnet_upload_file_close(struct webnet_session* session)
+{
+    int fd;
+    struct webnet_module_upload_session* upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    fd = upload_session->user_data;
+    if (fd >= 0)
+    {
+        return close(fd);
+    }
+    return -1;
+}
+
+int webnet_upload_file_write(struct webnet_session* session, const void* data, rt_size_t length)
+{
+    int fd;
+    struct webnet_module_upload_session* upload_session;
+
+    /* get upload session */
+    upload_session = (struct webnet_module_upload_session *)session->user_data;
+    fd = upload_session->user_data;
+    if (fd >= 0)
+    {
+        return write(fd, data, length);
+    }
+
+    return 0;
+}
+
+#endif /* WEBNET_USING_UPLOAD */

+ 81 - 0
samples/index.html

@@ -0,0 +1,81 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+  <head>
+    <title> WebNet </title>
+    <meta http-equiv="Content-Type" content="text/html; charset=gbk" />
+  </head>
+ 
+  <body>
+    <h1>WebNet</h1>
+    WebNet 是 RT-Thread RTOS 上的新一代 web server,贴身为小型嵌入式设备定制,具有低资源占用,扩展性好的特点。
+    <hr>
+    
+    <h2>WebNet 功能</h2>  
+    <ul>
+      <li>支持 HTTP 1.0/1.1 规范
+      <li>支持 AUTH 基本认证
+      <li>支持 CGI 事件处理
+      <li>支持 ASP 变量替换
+      <li>支持 SSI 文件嵌入
+      <li>支持 INDEX 目录显示
+      <li>支持 ALIAS 别名
+      <li>支持 upload 文件上传
+    </ul>
+    
+    <hr>
+    <h3>1. AUTH Test</h3>
+    WebNet 基本认证功能设计成与 URL 目录相挂钩,在使用时可以<b>根据目录划分访问权限</b>。
+    <br/><br/>
+    <a href="/admin/">基本认证功能测试:用户名及密码为 <b>admin:admin</b></a>
+    <br/><br/>
+    
+    <hr>
+    <h3>2. CGI Test</h3>
+    WebNet CGI 功能可以让用户执行指定的函数,CGI测试:
+    <br/><br/>
+    <a href="/cgi-bin/hello">> hello world</a>
+    <br/>
+    <a href="/cgi-bin/calc">> calc</a>
+    <br/><br/>
+    
+    <hr>
+    <h3>3. ASP Test</h3>
+    WebNet 实现的是一种简化方式的 ASP 变量替换,即当页面中有<b><% %></b> 标记出现时,将自动替换成用户注册的字符串并输。
+    <br/><br/>
+    <a href="/version.asp">ASP 功能测试:访问 version.asp 文件,打印当前 RT-Thread 系统版本号</a>
+    <br/><br/>
+    
+    <hr>
+    <h3>4. SSI Test</h3>
+    SSI 功能模块可以用于执行服务器上程序或者插入文本内容到网页中页面代码中有 <b>#include virtual="/xxx"</b> 或者 <b>#include file="/xxx"</b> 标记存在时,将自动解析成对应文件中的信息。
+    <br/><br/>
+    <a href="/index.shtml">SSI 功能测试:访问 /version.shtml 页面,其中嵌套了 /index.html 页面</a>
+    <br/><br/>
+    
+    <hr>
+    <h3>5. INDEX Test</h3>
+    INDEX 功能模块可以自动显示目录下文件列表,当访问目录中不存在 index.htm, index.html, index.asp 等文件时,将自动显示这个目录下的文件列表。
+    <br/><br/>
+    <a href="/admin/">INDEX 功能测试:访问/admin 目录,列出目录下所有文件</a>
+    <br/><br/>
+    
+    <hr>
+    <h3>6. ALIAS Test</h3>
+    ALIAS 别名模块可以给当前资源文件夹设置多个别名,可以用于<b>长路径的简化操作</b>,方便用户对资源的访问。
+    <br/><br/>
+    <a href="/test/">ALIAS 功能测试:访问 /test 目录会跳转到 /admin 目录</a>
+    <br/><br/>
+    
+    <hr>
+    <h3>7. Upload File Test</h3>
+    文件上传模块可以用于上传文件到指定的目录,这里上传到根目录下的 /upload 目录。
+    <br/><br/>
+    <form name="upload" method="POST" enctype="multipart/form-data" action="/upload">
+      <input type="file" name="file1" >
+      <input type="submit" name="submit" value="上传">
+    </form>
+    <br/>
+    <a href="/upload/">点击浏览上传文件的目录</a>
+    <br/><br/>
+  </body>
+</html>

+ 14 - 0
samples/index.shtml

@@ -0,0 +1,14 @@
+<html>
+  <head>
+    <title> SSI Test </title>
+  </head>
+
+  <body>
+    <h1> SSI Test</h1>
+    <font size=\"+2\">The <b>index.html</b> page embedded in the following page</font>
+    <br/><br/>
+    <a href="javascript:history.go(-1);">Go back to root</a>
+    <hr>
+    <!--#include virtual="/index.html" --> 
+  </body>
+</html>

+ 10 - 0
samples/version.asp

@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+  <head>
+    <title> ASP Test </title>
+  </head>
+
+  <body>
+    <% version %>
+  </body>
+</html>

+ 127 - 0
samples/wn_sample.c

@@ -0,0 +1,127 @@
+/*
+ * File      : wn_sample.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2018-10-26     ChenYong     First version
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <webnet.h>
+#include <wn_module.h>
+
+static void asp_var_version(struct webnet_session* session)
+{
+    RT_ASSERT(session != RT_NULL);
+
+    static const char *version = "<html><body><font size=\"+2\">RT-Thread %d.%d.%d</font><br><br>"
+                                "<a href=\"javascript:history.go(-1);\">Go back to root</a></html></body>";
+
+    webnet_session_printf(session, version, RT_VERSION, RT_SUBVERSION, RT_REVISION);
+}
+
+static void cgi_calc_handler(struct webnet_session* session)
+{
+    int a, b;
+    const char* mimetype;
+    struct webnet_request* request;
+    static const char* header = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; "
+                                "charset=gb2312\" /><title> calc </title></head>";
+
+    static const char* body = "<body><form method=\"post\" action=\"/cgi-bin/calc\"><input type=\"text\" name=\"a\" value=\"%d\"> "
+                              "+ <input type=\"text\" name=\"b\" value=\"%d\"> = %d<br><input type=\"submit\" value=\"\xBC\xC6\xCB\xE3\"></form>"
+                              "<br><a href=\"/index.html\">Go back to root</a></body></html>\r\n";
+
+    RT_ASSERT(session != RT_NULL);
+    request = session->request;
+    RT_ASSERT(request != RT_NULL);
+
+    /* get mimetype */
+    mimetype = mime_get_type(".html");
+
+    a = 1;
+    b = 1;
+    /* set http header */
+    session->request->result_code = 200;
+    webnet_session_set_header(session, mimetype, 200, "Ok", -1);
+
+    webnet_session_write(session, (const rt_uint8_t*)header, rt_strlen(header));
+    if (request->query_counter)
+    {
+        const char *a_value, *b_value;
+        a_value = webnet_request_get_query(request, "a");
+        b_value = webnet_request_get_query(request, "b");
+
+        a = atoi(a_value);
+        b = atoi(b_value);
+    }
+
+    webnet_session_printf(session, body, a, b, a + b);
+}
+
+static void cgi_hello_handler(struct webnet_session* session)
+{
+    const char* mimetype;
+    static const char* status = "<html><head><title> hello </title>"
+                                "</head><body><font size=\"+2\">hello world</font><br><br>"
+                                "<a href=\"javascript:history.go(-1);\">Go back to root</a></body></html>\r\n";
+    RT_ASSERT(session != RT_NULL);
+
+    /* get mimetype */
+    mimetype = mime_get_type(".html");
+
+    /* set http header */
+    session->request->result_code = 200;
+    webnet_session_set_header(session, mimetype, 200, "Ok", strlen(status));
+
+    webnet_session_write(session, (const rt_uint8_t*)status, rt_strlen(status));
+}
+
+void webnet_test(void)
+{
+#ifdef WEBNET_USING_CGI
+    webnet_cgi_register("hello", cgi_hello_handler);
+    webnet_cgi_register("calc", cgi_calc_handler);
+#endif
+
+#ifdef WEBNET_USING_ASP
+    webnet_asp_add_var("version", asp_var_version);
+#endif
+
+#ifdef WEBNET_USING_ALIAS
+    webnet_alias_set("/test", "/admin");
+#endif
+
+#ifdef WEBNET_USING_AUTH
+    webnet_auth_set("/admin", "admin:admin");
+#endif
+
+#ifdef WEBNET_USING_UPLOAD
+    extern const struct webnet_module_upload_entry upload_entry_upload;
+
+    webnet_upload_add(&upload_entry_upload);
+#endif
+
+    webnet_init();
+}
+#ifdef FINSH_USING_MSH
+MSH_CMD_EXPORT(webnet_test, wenbet test);
+#endif

+ 173 - 0
samples/wn_sample_upload.c

@@ -0,0 +1,173 @@
+/*
+ * File      : wn_sample_upload.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2018-10-26     ChenYong     First version
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <rtthread.h>
+#include <dfs_posix.h>
+
+#include <webnet.h>
+#include <wn_module.h>
+
+#ifdef WEBNET_USING_UPLOAD
+
+/**
+ * upload file.
+ */
+static const char * sd_upload = "/webnet";
+
+static const char * upload_dir = "upload"; /* e.g: "upload" */
+static int file_size = 0;
+
+const char *get_file_name(struct webnet_session *session)
+{
+    const char *path = RT_NULL, *path_last = RT_NULL;
+    
+    path_last = webnet_upload_get_filename(session);
+    if (path_last == RT_NULL)
+    {
+        rt_kprintf("file name err!!\n");
+        return RT_NULL;
+    }
+
+    path = strrchr(path_last, '\\');
+    if (path != RT_NULL)
+    {
+        path++;
+        path_last = path;
+    }
+
+    path = strrchr(path_last, '/');
+    if (path != RT_NULL)
+    {
+        path++;
+        path_last = path;
+    }
+    
+    return path_last;
+}
+
+static int upload_open(struct webnet_session *session)
+{
+    int fd;
+    const char *file_name = RT_NULL;
+    
+    file_name = get_file_name(session);
+    rt_kprintf("Upload FileName: %s\n", file_name);
+    rt_kprintf("Content-Type   : %s\n", webnet_upload_get_content_type(session));
+
+    if (webnet_upload_get_filename(session) != RT_NULL)
+    {
+        int path_size;
+        char * file_path;
+
+        path_size = strlen(sd_upload) + strlen(upload_dir)
+                    + strlen(file_name);
+
+        path_size += 4;
+        file_path = (char *)rt_malloc(path_size);
+
+        if(file_path == RT_NULL)
+        {
+            fd = -1;
+            goto _exit;
+        }
+
+        sprintf(file_path, "%s/%s/%s", sd_upload, upload_dir, file_name);
+
+        rt_kprintf("save to: %s\r\n", file_path);
+
+        fd = open(file_path, O_WRONLY | O_CREAT, 0);
+        if (fd < 0)
+        {
+            webnet_session_close(session);
+            rt_free(file_path);
+
+            fd = -1;
+            goto _exit;
+        }
+    }
+
+    file_size = 0;
+
+_exit:
+    return (int)fd;
+}
+
+static int upload_close(struct webnet_session* session)
+{
+    int fd;
+
+    fd = (int)webnet_upload_get_userdata(session);
+    if (fd < 0) return 0;
+
+    close(fd);
+    rt_kprintf("Upload FileSize: %d\n", file_size);
+    return 0;
+}
+
+static int upload_write(struct webnet_session* session, const void* data, rt_size_t length)
+{
+    int fd;
+
+    fd = (int)webnet_upload_get_userdata(session);
+    if (fd < 0) return 0;
+
+    rt_kprintf("write: length %d\n", length);
+
+    write(fd, data, length);
+    file_size += length;
+
+    return length;
+}
+
+static int upload_done (struct webnet_session* session)
+{
+    const char* mimetype;
+    static const char* status = "<html><head><title>Upload OK </title>"
+                                "</head><body>Upload OK, file length = %d "
+                                "<br/><br/><a href=\"javascript:history.go(-1);\">"
+                                "Go back to root</a></body></html>\r\n";
+
+    /* get mimetype */
+    mimetype = mime_get_type(".html");
+
+    /* set http header */
+    session->request->result_code = 200;
+    webnet_session_set_header(session, mimetype, 200, "Ok", rt_strlen(status));
+    webnet_session_printf(session, status, file_size);
+
+    return 0;
+}
+
+const struct webnet_module_upload_entry upload_entry_upload =
+{
+    "/upload",
+    upload_open,
+    upload_close,
+    upload_write,
+    upload_done
+};
+
+#endif /* WEBNET_USING_UPLOAD */

+ 202 - 0
src/webnet.c

@@ -0,0 +1,202 @@
+/*
+ * File      : webnet.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2012-07-03     Bernard      Add webnet port and webroot setting interface.
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <webnet.h>
+#include <wn_module.h>
+
+#ifdef SAL_USING_POSIX
+#include <sys/select.h>
+#else
+#include <lwip/select.h>
+#endif
+
+#define DBG_ENABLE
+#define DBG_COLOR
+#define DBG_SECTION_NAME    "wn"
+#ifdef WEBNET_USING_LOG
+#define DBG_LEVEL           DBG_LOG
+#else
+#define DBG_LEVEL           DBG_INFO
+#endif /* WEBNET_USING_LOG */
+#include <rtdbg.h>
+
+static rt_uint16_t webnet_port = WEBNET_PORT;
+static char webnet_root[64] = WEBNET_ROOT;
+static rt_bool_t init_ok = RT_FALSE;
+
+void webnet_set_port(int port)
+{
+    RT_ASSERT(init_ok == RT_FALSE);
+    webnet_port = port;
+}
+
+int webnet_get_port(void)
+{
+    return webnet_port;
+}
+
+void webnet_set_root(const char* webroot_path)
+{
+    rt_strncpy(webnet_root, webroot_path, sizeof(webnet_root) - 1);
+    webnet_root[sizeof(webnet_root) - 1] = '\0';
+}
+
+const char* webnet_get_root(void)
+{
+    return webnet_root;
+}
+
+/**
+ * webnet thread entry
+ */
+static void webnet_thread(void *parameter)
+{
+    int listenfd = -1;
+    fd_set readset, tempfds;
+    fd_set writeset, tempwrtfds;
+    int sock_fd, maxfdp1;
+    struct sockaddr_in webnet_saddr;
+
+    /* First acquire our socket for listening for connections */
+    listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+    if (listenfd < 0)
+    {
+        LOG_E("Create socket failed.");
+        goto __exit;
+    }
+
+    rt_memset(&webnet_saddr, 0, sizeof(webnet_saddr));
+    webnet_saddr.sin_family = AF_INET;
+    webnet_saddr.sin_addr.s_addr = htonl(INADDR_ANY);
+    webnet_saddr.sin_port = htons(webnet_port); /* webnet server port */
+
+    if (bind(listenfd, (struct sockaddr *) &webnet_saddr, sizeof(webnet_saddr)) == -1)
+    {
+        LOG_E("Bind socket failed, errno=%d\n", errno);
+        goto __exit;
+    }
+
+    /* Put socket into listening mode */
+    if (listen(listenfd, WEBNET_CONN_MAX) == -1)
+    {
+        LOG_E("Socket listen(%d) failed.", WEBNET_CONN_MAX);
+        goto __exit;
+    }
+
+    /* initialize module (no session at present) */
+    webnet_module_handle_event(RT_NULL, WEBNET_EVENT_INIT);
+
+    /* Wait forever for network input: This could be connections or data */
+    for (;;)
+    {
+        /* Determine what sockets need to be in readset */
+        FD_ZERO(&readset);
+        FD_ZERO(&writeset);
+        FD_SET(listenfd, &readset);
+
+        /* set fds in each sessions */
+        maxfdp1 = webnet_sessions_set_fds(&readset, &writeset);
+        if (maxfdp1 < listenfd + 1)
+        {
+            maxfdp1 = listenfd + 1;
+        }
+
+        /* use temporary fd set in select */
+        tempfds = readset;
+        tempwrtfds = writeset;
+        /* Wait for data or a new connection */
+        sock_fd = select(maxfdp1, &tempfds, &tempwrtfds, 0, 0);
+        if (sock_fd == 0)
+        {
+            continue;
+        }
+
+        /* At least one descriptor is ready */
+        if (FD_ISSET(listenfd, &tempfds))
+        {
+            struct webnet_session* accept_session;
+            /* We have a new connection request */
+            accept_session = webnet_session_create(listenfd);
+            if (accept_session == RT_NULL)
+            {
+                /* create session failed, just accept and then close */
+                int sock;
+                struct sockaddr cliaddr;
+                socklen_t clilen;
+
+                clilen = sizeof(struct sockaddr_in);
+                sock = accept(listenfd, &cliaddr, &clilen);
+                if (sock >= 0)
+                {
+                    closesocket(sock);
+                }
+            }
+            else
+            {
+                /* add read fdset */
+                FD_SET(accept_session->socket, &readset);
+            }
+        }
+
+        webnet_sessions_handle_fds(&tempfds, &writeset);
+    }
+
+__exit:
+    if (listenfd >= 0)
+    {
+        closesocket(listenfd);
+    }
+}
+
+int webnet_init(void)
+{
+    rt_thread_t tid;
+
+    if (init_ok == RT_TRUE)
+    {
+        LOG_I("RT-Thread webnet package is already initialized.");
+        return 0;
+    }
+
+    tid = rt_thread_create(WEBNET_THREAD_NAME,
+                           webnet_thread, RT_NULL,
+                           WEBNET_THREAD_STACKSIZE, WEBNET_PRIORITY, 5);
+
+    if (tid != RT_NULL)
+    {
+        rt_thread_startup(tid);
+        init_ok = RT_TRUE;
+        LOG_I("RT-Thread webnet package (V%s) initialize success.", WEBNET_VERSION);
+    }
+    else
+    {
+        LOG_E("RT-Thread webnet package (V%s) initialize failed.", WEBNET_VERSION);
+        return -1;
+    }
+
+    return 0;
+}

+ 84 - 0
src/wn_mimetype.c

@@ -0,0 +1,84 @@
+/*
+ * File      : wn_mimetype.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ */
+
+#include <string.h>
+#include <rtthread.h>
+
+#include <wn_utils.h>
+
+struct webnet_mime_entry
+{
+    const char* name;
+    const char* type;
+};
+
+static const struct webnet_mime_entry mime_tables[] =
+{
+    { "any",	"application/binary" }, /* default. */
+    { "html",	"text/html" },
+    { "htm",	"text/html" },
+    { "css",	"text/css" },
+    { "txt",	"text/plain" },
+    { "pdf",	"application/pdf" },
+    { "gif",	"image/gif" },
+    { "png",	"image/png" },
+    { "jpeg",	"image/jpeg" },
+    { "jpg",	"image/jpeg" },
+    { "avi",	"video/x-msvideo" },
+    { "mp3",	"audio/mpeg" },
+    { "ogg",	"audio/x-oggvorbis" },
+    { "wav",	"audio/x-wav" },
+    { "class",	"application/octet-stream" },
+    { "js",		"application/x-javascript" },
+    { "tar",	"application/x-tar" },
+    { "zip",	"application/zip" },
+    { "xml",	"text/xml" },
+    { RT_NULL,  RT_NULL }
+};
+
+/**
+ * get mime type according to URL
+ */
+const char* mime_get_type(const char* url)
+{
+    rt_uint32_t index;
+
+    index = 0;
+    if (url == RT_NULL)
+    {
+        return mime_tables[0].type;
+    }
+
+    while (mime_tables[index].name != RT_NULL)
+    {
+        if (str_end_with(url, mime_tables[index].name))
+        {
+            return mime_tables[index].type;
+        }
+
+        index++;
+    }
+
+    /* return text/html as default */
+    return mime_tables[0].type;
+}

+ 561 - 0
src/wn_module.c

@@ -0,0 +1,561 @@
+/*
+ * File      : wn_module.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2012-06-25     Bernard      add SSI and Upload module
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+#ifdef RT_USING_DFS
+#include <dfs_posix.h>
+#endif
+
+static int _webnet_module_system_init(struct webnet_session *session, int event)
+{
+#ifdef WEBNET_USING_LOG
+    webnet_module_log(session, event);
+#endif
+
+#ifdef WEBNET_USING_SSL
+    webnet_module_ssl(session, event);
+#endif
+
+#ifdef WEBNET_USING_CGI
+    webnet_module_cgi(session, event);
+#endif
+
+#ifdef WEBNET_USING_DMR
+    webnet_module_dmr(session, event);
+#endif
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+static int _webnet_module_system_uri_physical(struct webnet_session *session, int event)
+{
+    int result;
+    result = WEBNET_MODULE_CONTINUE;
+
+#ifdef WEBNET_USING_LOG
+    webnet_module_log(session, event);
+#endif
+
+#ifdef WEBNET_USING_ALIAS
+    result = webnet_module_alias(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+#ifdef WEBNET_USING_AUTH
+    result = webnet_module_auth(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+#ifdef WEBNET_USING_CGI
+    result = webnet_module_cgi(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+#ifdef WEBNET_USING_DMR
+    result = webnet_module_dmr(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+#ifdef WEBNET_USING_UPLOAD
+    result = webnet_module_upload(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+    return result;
+}
+
+static void _webnet_dofile_handle(struct webnet_session *session, int event)
+{
+    int fd = session->user_data;
+
+    if (event & WEBNET_EVENT_WRITE)
+    {
+        rt_size_t readbytes;
+        rt_size_t length = RT_ALIGN_DOWN(WEBNET_SESSION_BUFSZ, 4);
+#ifdef WEBNET_USING_RANGE
+        if(session->request->Range)
+        {
+            length = session->request->pos_end - session->request->pos_start;
+            if(length == 0)
+            {
+                goto __exit;
+            }
+       
+            if(length > WEBNET_SESSION_BUFSZ)
+            {
+                length = WEBNET_SESSION_BUFSZ;
+            }
+           lseek(fd, session->request->pos_start, SEEK_SET);
+           session->request->pos_start += length;
+        }
+#endif            
+        readbytes = read(fd, session->buffer, length);
+        if (readbytes <= 0) /* end of file */
+            goto __exit;
+
+        if (webnet_session_write(session, session->buffer, readbytes) == 0)
+            goto __exit;
+        return;
+    }
+
+__exit:
+    close(fd);
+    session->user_data = 0;
+    session->session_event_mask = 0; /* clean event */
+    /* destroy session */
+    session->session_phase = WEB_PHASE_CLOSE;
+
+    return;
+}
+
+static const struct webnet_session_ops _dofile_ops =
+{
+    _webnet_dofile_handle,
+    RT_NULL
+};
+
+/* send a file to http client */
+int webnet_module_system_dofile(struct webnet_session *session)
+{
+    int fd = -1;    /* file descriptor */
+    const char *mimetype;
+    rt_size_t file_length;
+    struct webnet_request *request;
+
+#if WEBNET_CACHE_LEVEL > 0
+    char ctime_str[32];
+    int stat_result = -1;
+#endif /* WEBNET_CACHE_LEVEL */
+
+    RT_ASSERT(session != RT_NULL);
+    request = session->request;
+    RT_ASSERT(request != RT_NULL);
+
+#if WEBNET_CACHE_LEVEL > 0
+#ifdef WEBNET_USING_GZIP
+    /* get .gz Last-Modified. */
+    if (request->support_gzip)
+    {
+        struct stat file_stat;
+
+        char *path_gz = wn_malloc(strlen(request->path) + 4);  /* ".gz\0" */
+
+        if (path_gz != RT_NULL)
+        {
+            sprintf(path_gz, "%s.gz", request->path);
+            stat_result = stat(request->path, &file_stat);
+            wn_free(path_gz);
+        }
+
+        if (stat_result == 0)
+        {
+            rt_enter_critical();
+            strcpy(ctime_str, ctime((time_t *)&file_stat.st_mtime));
+            rt_exit_critical();
+
+            ctime_str[strlen(ctime_str) - 1] = '\0'; /* clear the end \n */
+
+            if ((request->modified != RT_NULL)
+                    && (strcmp(request->modified, ctime_str) == 0))
+            {
+                request->result_code = 304;
+                return WEBNET_MODULE_FINISHED;
+            }
+        }
+    }
+
+    /* .gz not exist, use raw. */
+#endif /* WEBNET_USING_GZIP */
+    /* get Last-Modified. */
+    if (stat_result != 0)
+    {
+        struct stat file_stat;
+        stat_result = stat(request->path, &file_stat);
+
+        if (stat_result == 0)
+        {
+            rt_enter_critical();
+            strcpy(ctime_str, ctime((time_t *)&file_stat.st_mtime));
+            rt_exit_critical();
+
+            ctime_str[strlen(ctime_str) - 1] = '\0'; /* clear the end \n */
+
+            if ((request->modified != RT_NULL)
+                    && (strcmp(request->modified, ctime_str) == 0))
+            {
+                request->result_code = 304;
+                return WEBNET_MODULE_FINISHED;
+            }
+        }
+    }
+#endif /* WEBNET_CACHE_LEVEL > 0 */
+
+    /* get mime type */
+    mimetype = mime_get_type(request->path);
+
+#ifdef WEBNET_USING_GZIP
+    if (request->support_gzip)
+    {
+        char *path_gz = wn_malloc(strlen(request->path) + 4);  /* ".gz\0" */
+
+        if (path_gz != RT_NULL)
+        {
+            sprintf(path_gz, "%s.gz", request->path);
+            fd = open(path_gz, O_RDONLY, 0);
+            wn_free(path_gz);
+
+            if (fd < 0)
+            {
+                /* .gz not exist, use raw. */
+                request->support_gzip = RT_FALSE;
+            }
+        }
+    }
+
+    /* .gz not exist, use raw. */
+#endif /* WEBNET_USING_GZIP */
+    if (fd < 0)
+    {
+        fd = open(request->path, O_RDONLY, 0);
+    }
+
+    if (fd < 0)
+    {
+        request->result_code = 404;
+        return WEBNET_MODULE_FINISHED;
+    }
+
+    /* get file size */
+    file_length = lseek(fd, 0, SEEK_END);
+    /* seek to beginning of file */
+    lseek(fd, 0, SEEK_SET);
+
+    /*************todo**********************/
+#ifdef WEBNET_USING_RANGE
+    if (request->Range)
+    {
+        char *range_start, *range_end;
+        int32_t pos_start = 0;
+        uint32_t pos_end = file_length - 1;
+        range_start = strstr(request->Range, "bytes=");
+        if (range_start)
+        {
+            range_start += 6;
+            range_end = strstr(range_start, "-");
+            if (range_start == range_end)
+                pos_start = 0;
+            else
+                pos_start = atoi(range_start);
+
+            /* send file to remote */
+            if ((!range_end) || (strstr(range_start, ",")))
+            {
+                request->result_code = 400;
+                goto _error_exit;
+            }
+            if (range_end)
+            {
+                *range_end = '\0';
+                range_end += 1;
+                pos_end = atoi(range_end);
+            }
+        }
+#ifdef WEBNET_USING_GZIP
+        if (request->support_gzip)
+        {
+            pos_start = 0; /*  */
+        }
+#endif /* WEBNET_USING_GZIP */
+        if ((pos_start >= file_length) || (pos_end >= file_length))
+        {
+            request->result_code = 416;
+            webnet_session_set_header_status_line(session, request->result_code, "Requested Range Not Satisfiable");
+            goto _error_exit;
+        }
+        if (lseek(fd, pos_start, SEEK_SET) != pos_start)
+        {
+            request->result_code = 500;
+            goto _error_exit;
+        }
+        if (pos_end == 0)
+        {
+            pos_end = file_length - 1;
+        }
+        file_length = pos_end - pos_start + 1;
+        request->result_code = 216;
+        request->pos_start = pos_start;
+        request->pos_end = pos_end;
+        webnet_session_set_header_status_line(session, request->result_code, "Partial Content");
+        webnet_session_printf(session, "Content-Range: %d-%d/%d\r\n", pos_start, pos_end, file_length);
+    }else
+
+#endif /* WEBNET_USING_RANGE */
+
+    /*************todo**********************/
+    {
+        /* send file to remote */
+        request->result_code = 200;
+        webnet_session_set_header_status_line(session, request->result_code, "OK");
+    }
+#if WEBNET_CACHE_LEVEL > 0
+    /* send Last-Modified. */
+    webnet_session_printf(session,
+                          "Last-Modified: %s\r\n",
+                          ctime_str);
+#endif /* WEBNET_CACHE_LEVEL > 0 */
+
+#if WEBNET_CACHE_LEVEL > 1
+    /* Cache-Control. */
+    webnet_session_printf(session,
+                          "Cache-Control: max-age=%d\r\n",
+                          WEBNET_CACHE_MAX_AGE);
+#endif /* WEBNET_CACHE_LEVEL > 1 */
+
+    /* send Content-Type. */
+    webnet_session_printf(session,
+                          "Content-Type: %s\r\n",
+                          mimetype);
+
+    /* send Content-Length. */
+    webnet_session_printf(session,
+                          "Content-Length: %ld\r\n",
+                          file_length);
+
+#ifdef WEBNET_USING_GZIP
+    if (request->support_gzip)
+    {
+        /* gzip deflate. */
+        webnet_session_printf(session, "Content-Encoding: gzip\r\n");
+    }
+#endif /* WEBNET_USING_GZIP */
+
+    /* send Access-Control-Allow-Origin. */
+    webnet_session_printf(session, "Access-Control-Allow-Origin:*\r\n");
+
+    /* send http header end. */
+    webnet_session_printf(session, "\r\n");
+
+    if (file_length <= 0)
+    {
+        close(fd);
+        return WEBNET_MODULE_FINISHED;
+    }
+
+    /*
+     * set session write context
+     */
+    if (request->method != WEBNET_HEADER)
+    {
+        /* set dofile session ops */
+        session->session_event_mask = WEBNET_EVENT_WRITE;
+        session->user_data = (rt_uint32_t)fd;
+        session->session_ops = &_dofile_ops;
+    }
+    return WEBNET_MODULE_FINISHED;
+
+_error_exit:
+    if (fd >= 0)
+    {
+        close(fd);
+    }
+
+    return WEBNET_MODULE_FINISHED;
+}
+
+static int _webnet_module_system_uri_post(struct webnet_session *session, int event)
+{
+    int result;
+    result = WEBNET_MODULE_CONTINUE;
+
+#ifdef WEBNET_USING_LOG
+    webnet_module_log(session, event);
+#endif
+
+#ifdef WEBNET_USING_LUA
+    result = webnet_module_lua(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+#ifdef WEBNET_USING_ASP
+    result = webnet_module_asp(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+#ifdef WEBNET_USING_SSI
+    result = webnet_module_ssi(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+#ifdef WEBNET_USING_DAV
+    result = webnet_module_dav(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+#ifdef WEBNET_USING_INDEX
+    result = webnet_module_dirindex(session, event);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+#endif
+
+    /* always module finished in dofile */
+    result = webnet_module_system_dofile(session);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+static int _webnet_module_system_response_header(struct webnet_session *session, int event)
+{
+    int result;
+    result = WEBNET_MODULE_CONTINUE;
+
+    return result;
+}
+
+static int _webnet_module_system_response_file(struct webnet_session *session, int event)
+{
+    int result;
+    result = WEBNET_MODULE_CONTINUE;
+
+    return result;
+}
+
+int webnet_module_handle_event(struct webnet_session *session, int event)
+{
+    switch (event)
+    {
+    case WEBNET_EVENT_INIT:
+        return _webnet_module_system_init(session, event);
+    case WEBNET_EVENT_URI_PHYSICAL:
+        return _webnet_module_system_uri_physical(session, event);
+    case WEBNET_EVENT_URI_POST:
+        return _webnet_module_system_uri_post(session, event);
+    case WEBNET_EVENT_RSP_HEADER:
+        return _webnet_module_system_response_header(session, event);
+    case WEBNET_EVENT_RSP_FILE:
+        return _webnet_module_system_response_file(session, event);
+    default:
+        RT_ASSERT(0);
+        break;
+    }
+
+    return WEBNET_MODULE_CONTINUE;
+}
+
+/* default index file */
+static const char *default_files[] =
+{
+    "",
+    "/index.html",
+    "/index.htm",
+    RT_NULL
+};
+
+/**
+ * handle uri
+ * there are two phases on uri handling:
+ * - map url to physical
+ * - url handling
+ */
+int webnet_module_handle_uri(struct webnet_session *session)
+{
+    int result, fd;
+    char *full_path;
+    rt_uint32_t index;
+    struct webnet_request *request;
+
+    RT_ASSERT(session != RT_NULL);
+    /* get request */
+    request = session->request;
+    RT_ASSERT(request != RT_NULL);
+
+    /* map uri to physical */
+    result = webnet_module_handle_event(session, WEBNET_EVENT_URI_PHYSICAL);
+    if (result == WEBNET_MODULE_FINISHED) return result;
+
+    /* made a full physical path */
+    full_path = (char *) wn_malloc(WEBNET_PATH_MAX);
+    RT_ASSERT(full_path != RT_NULL);
+
+    /* only GET or POST need try default page. */
+    if ((session->request->method != WEBNET_GET)
+            && (session->request->method != WEBNET_POST))
+    {
+        index = sizeof(default_files) / sizeof(default_files[0]);
+        index -= 1;
+
+        goto _end_default_files;
+    }
+
+    index = 0;
+    while (default_files[index] != RT_NULL)
+    {
+        /* made a full path */
+        rt_snprintf(full_path, WEBNET_PATH_MAX, "%s/%s%s",
+                    webnet_get_root(), request->path, default_files[index]);
+        /* normalize path */
+        str_normalize_path(full_path);
+
+        fd = open(full_path, O_RDONLY, 0);
+        if (fd >= 0)
+        {
+            /* close file descriptor */
+            close(fd);
+            break;
+        }
+
+        index ++;
+    }
+_end_default_files:
+
+    /* no this file */
+    if (default_files[index] == RT_NULL)
+    {
+        /* use old full path */
+        rt_snprintf(full_path, WEBNET_PATH_MAX, "%s/%s", webnet_get_root(), request->path);
+        /* normalize path */
+        str_normalize_path(full_path);
+    }
+
+    /* mark path as full physical path */
+    wn_free(request->path);
+    request->path = full_path;
+
+    /* check uri valid */
+    if (!str_begin_with(request->path, webnet_get_root()))
+    {
+        /* not found */
+        request->result_code = 404;
+        return WEBNET_MODULE_FINISHED;
+    }
+
+    /* uri post handle */
+    return webnet_module_handle_event(session, WEBNET_EVENT_URI_POST);
+}

+ 950 - 0
src/wn_request.c

@@ -0,0 +1,950 @@
+/*
+ * File      : wn_request.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2011-08-05     Bernard      fixed the query issue
+ * 2011-08-18     Bernard      fixed chrome query issue
+ * 2011-11-12     Bernard      fixed Content-Length zero issue in POST.
+ * 2012-06-25     Bernard      added parsing for Content-Type
+ * 2012-09-15     Bernard      fixed basic authentication issue in Safari.
+ * 2012-10-27     Bernard      fixed the upload issue in FireFox.
+ * 2012-12-17     Bernard      fixed the content multi-transmission issue in POST.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <webnet.h>
+#include <wn_request.h>
+#include <wn_utils.h>
+
+#define DBG_ENABLE
+#define DBG_COLOR
+#define DBG_SECTION_NAME    "wn.request"
+#ifdef WEBNET_USING_LOG
+#define DBG_LEVEL           DBG_LOG
+#else
+#define DBG_LEVEL           DBG_INFO
+#endif /* WEBNET_USING_LOG */
+#include <rtdbg.h>
+
+#define POST_BUFSZ_MAX      (8 * 1024)
+
+/**
+ * parse a query
+ */
+static void _webnet_request_parse_query(struct webnet_request* request)
+{
+    char *ptr, *end_ptr;
+    rt_uint32_t index;
+
+    if ((request->query == RT_NULL) || (*request->query == '\0')) return; /* no query */
+
+    /* get the query counter */
+    ptr = request->query;
+    end_ptr = request->query + request->content_length;
+
+    request->query_counter = 1;
+    while (*ptr && ptr <= end_ptr)
+    {
+        if (*ptr == '&')
+        {
+            while ((*ptr == '&') && (*ptr != '\0')) ptr ++;
+            if (*ptr == '\0') break;
+
+            request->query_counter ++;
+        }
+        ptr ++;
+    }
+    if (request->query_counter == 0) return; /* no query */
+
+    /* allocate query item */
+    request->query_items = (struct webnet_query_item*) wn_malloc (sizeof(struct webnet_query_item)
+                           * request->query_counter);
+    if (request->query_items == RT_NULL)
+    {
+        request->result_code = 500;
+        return;
+    }
+
+    /* parse the query */
+    ptr = request->query;
+    for (index = 0; index < request->query_counter; index ++)
+    {
+        request->query_items[index].name = ptr;
+        request->query_items[index].value = RT_NULL;
+
+        /* get value or goto next item */
+        while ((*ptr != '&') && (*ptr != '\0'))
+        {
+            /* get value */
+            if (*ptr == '=')
+            {
+                *ptr = '\0';
+                ptr ++;
+                request->query_items[index].value = ptr;
+                urldecode(request->query_items[index].value, strlen(request->query_items[index].value));
+            }
+            else ptr ++;
+        }
+
+        if (*ptr == '\0') break;
+        if (*ptr == '&')
+        {
+            /* make a item */
+            *ptr = '\0';
+            ptr ++;
+            while (*ptr == '&' && *ptr != '\0' && ptr <= end_ptr)ptr ++;
+            if (*ptr == '\0') break;
+        }
+    }
+}
+
+/**
+ * copy string from request and the field_copied set to TRUE
+ */
+static void _webnet_request_copy_str(struct webnet_request* request)
+{
+    if (request->path != RT_NULL) request->path = wn_strdup(request->path);
+    if (request->host != RT_NULL)
+    {
+        char *ptr;
+        ptr = request->host;
+        while (*ptr && *ptr != ':') ptr ++;
+        if (*ptr == ':') *ptr = '\0';
+
+        request->host = wn_strdup(request->host);
+    }
+    if (request->cookie != RT_NULL) request->cookie = wn_strdup(request->cookie);
+    if (request->user_agent != RT_NULL) request->user_agent = wn_strdup(request->user_agent);
+    if (request->authorization != RT_NULL) request->authorization = wn_strdup(request->authorization);
+    if (request->accept_language != RT_NULL) request->accept_language = wn_strdup(request->accept_language);
+    if (request->referer != RT_NULL) request->referer = wn_strdup(request->referer);
+    if (request->content_type != RT_NULL) request->content_type = wn_strdup(request->content_type);
+
+    /* DMR */
+    if (request->callback) request->callback = wn_strdup(request->callback);
+    if (request->soap_action) request->soap_action = wn_strdup(request->soap_action);
+    if (request->sid) request->sid = wn_strdup(request->sid);
+
+    /* DAV */
+#ifdef WEBNET_USING_DAV
+    if (request->depth) request->depth = wn_strdup(request->depth);
+#endif
+#ifdef WEBNET_USING_RANGE
+    if (request->Range) request->Range = wn_strdup(request->Range);
+#endif /* WEBNET_USING_RANGE */
+    request->field_copied = RT_TRUE;
+}
+
+/**
+ * to check whether a query on the http request.
+ */
+rt_bool_t webnet_request_has_query(struct webnet_request* request, char* name)
+{
+    rt_uint32_t index;
+
+    for (index = 0; index < request->query_counter; index ++)
+    {
+        if (strncmp(request->query_items[index].name, name, strlen(name)) == 0)
+            return RT_TRUE;
+    }
+
+    return RT_FALSE;
+}
+RTM_EXPORT(webnet_request_has_query);
+
+/**
+ * get query value according to the name
+ */
+const char* webnet_request_get_query(struct webnet_request* request, char* name)
+{
+    rt_uint32_t index;
+
+    for (index = 0; index < request->query_counter; index ++)
+    {
+        if (strncmp(request->query_items[index].name, name, strlen(name)) == 0)
+            return request->query_items[index].value;
+    }
+
+    return RT_NULL;
+}
+RTM_EXPORT(webnet_request_get_query);
+
+struct web_method
+{
+    const char *method_str;
+    enum webnet_method method_value;
+};
+
+const struct web_method methods [] = {
+    {"GET ",        WEBNET_GET},
+    {"POST ",       WEBNET_POST},
+    {"HEADER ",     WEBNET_HEADER},
+    {"HEAD ",       WEBNET_HEAD},
+    {"PUT ",        WEBNET_PUT},
+    {"OPTIONS ",    WEBNET_OPTIONS},
+    {"PROPFIND ",   WEBNET_PROPFIND},
+    {"PROPPATCH ",  WEBNET_PROPPATCH},
+    {"DELETE ",     WEBNET_DELETE},
+    {"CONNECT ",    WEBNET_CONNECT},
+    {"MKCOL ",      WEBNET_MKCOL},
+    {"MOVE ",       WEBNET_MOVE},
+    {"SUBSCRIBE ",  WEBNET_SUBSCRIBE},
+    {"UNSUBSCRIBE ", WEBNET_UNSUBSCRIBE},
+    {"NOTIFY ",     WEBNET_NOTIFY},
+    {NULL, WEBNET_UNKNOWN},
+};
+
+int webnet_request_parse_method(struct webnet_request *request, char* buffer, int length)
+{
+    char *ch;
+    int index;
+    char *request_buffer;
+    const struct web_method *method;
+
+    RT_ASSERT(request != RT_NULL);
+    RT_ASSERT(request->session != RT_NULL);
+
+    request_buffer = buffer;
+
+    if (strstr(request_buffer, "\r\n") == RT_NULL) return 0;
+
+    /* parse method */
+    for (index = 0; ; index ++)
+    {
+        method = &methods[index];
+        if (method->method_value == WEBNET_UNKNOWN)
+        {
+            /* Not implemented for other method */
+            request->result_code = 501;
+            return 0;
+        }
+
+        if (str_begin_with(request_buffer, method->method_str))
+        {
+            request->method = method->method_value;
+            request_buffer += strlen(method->method_str);
+            break;
+        }
+    }
+
+    /* get path */
+    ch = strchr(request_buffer, ' ');
+    if (ch == RT_NULL)
+    {
+        /* Bad Request */
+        request->result_code = 400;
+        return request_buffer - buffer;
+    }
+    *ch++ = '\0';
+    request->path = wn_strdup(request_buffer);
+    request_buffer = ch;
+
+    /* check path, whether there is a query */
+    ch = strchr(request->path, '?');
+    if (ch != RT_NULL)
+    {
+        *ch++ = '\0';
+        while (*ch == ' ') ch ++;
+
+        /* copy query and parse query */
+        request->query = wn_strdup(ch);
+        /* copy query and parse parameter */
+        _webnet_request_parse_query(request);
+    }
+
+    /* check protocol */
+    if (!str_begin_with(request_buffer, "HTTP/1"))
+    {
+        /* Not implemented, webnet does not support HTTP 0.9 protocol */
+        request->result_code = 501;
+        return request_buffer - buffer;
+    }
+
+    ch = strstr(request_buffer, "\r\n");
+    *ch ++ = '\0';
+    *ch ++ = '\0';
+    request_buffer = ch;
+
+    /* move to next phase */
+    request->session->session_phase = WEB_PHASE_HEADER;
+
+    return request_buffer - buffer;
+}
+
+int webnet_request_parse_header(struct webnet_request *request, char* buffer, int length)
+{
+    char *ch;
+    char *request_buffer;
+    struct webnet_session *session;
+
+    RT_ASSERT(request != RT_NULL);
+    RT_ASSERT(request->session != RT_NULL);
+
+    session = request->session;
+    request_buffer = buffer;
+
+    for (;;)
+    {
+        if (str_begin_with(request_buffer, "\r\n"))
+        {
+            /* end of http request */
+            request_buffer += 2;
+
+            if (request->content_length && request->method == WEBNET_POST)
+            {
+                session->session_phase = WEB_PHASE_RESPONSE;
+                if (!str_begin_with(request->content_type, "multipart/form-data") &&
+                    request->content_length < POST_BUFSZ_MAX)
+                {
+                    session->session_phase = WEB_PHASE_QUERY;
+
+                    /* allocate query buffer */
+                    request->query = (char*) wn_malloc(request->content_length + 1);
+                    rt_memset(request->query, 0, request->content_length + 1);
+                    request->query_offset = 0;
+                }
+            }
+            else
+            {
+                /* end of http request */
+                request->result_code = 200;
+                /* move to next phase */
+                session->session_phase = WEB_PHASE_RESPONSE;
+            }
+            break;
+        }
+
+        if (*request_buffer == '\0')
+        {
+            /* not the end of http request */
+            return request_buffer - buffer;
+        }
+
+        ch = strstr(request_buffer, "\r\n");
+        if (ch == RT_NULL)
+        {
+            /* not the end of http header request line */
+            return request_buffer - buffer;
+        }
+        /* set terminal field */
+        *ch ++ = '\0';
+        *ch ++ = '\0';
+
+        if (str_begin_with(request_buffer, "Host:"))
+        {
+            /* get host */
+            request_buffer += 5;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->host = wn_strdup(request_buffer);
+        }
+        else if (str_begin_with(request_buffer, "User-Agent:"))
+        {
+            /* get user agent */
+            request_buffer += 11;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->user_agent = wn_strdup(request_buffer);
+        }
+        else if (str_begin_with(request_buffer, "Accept-Language:"))
+        {
+            /* get accept language */
+            request_buffer += 16;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->accept_language = wn_strdup(request_buffer);
+        }
+        else if (str_begin_with(request_buffer, "Content-Length:"))
+        {
+            /* get content length */
+            request_buffer += 15;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->content_length = atoi(request_buffer);
+        }
+        else if (str_begin_with(request_buffer, "Content-Type:"))
+        {
+            /* get content type */
+            request_buffer += 13;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->content_type = wn_strdup(request_buffer);
+        }
+        else if (str_begin_with(request_buffer, "Referer:"))
+        {
+            /* get referer */
+            request_buffer += 8;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->referer = wn_strdup(request_buffer);
+        }
+#ifdef WEBNET_USING_RANGE
+        else if (str_begin_with(request_buffer, "Range:"))
+        {
+            /* get range */
+            request_buffer += 6;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->Range = wn_strdup(request_buffer);
+        }
+#endif /* WEBNET_USING_RANGE */
+#ifdef WEBNET_USING_DAV
+        else if(str_begin_with(request_buffer, "Depth:"))
+        {
+            request_buffer += 6;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->depth = wn_strdup(request_buffer);
+        }
+        else if (str_begin_with(request_buffer, "Destination:"))
+        {
+            request_buffer += 12;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->destination = wn_strdup(request_buffer);
+        }
+#endif /* WEBNET_USING_DAV */
+#ifdef WEBNET_USING_KEEPALIVE
+        else if (str_begin_with(request_buffer, "Connection:"))
+        {
+            /* set default connection to keep alive */
+            request->connection = WEBNET_CONN_KEEPALIVE;
+
+            /* get connection */
+            request_buffer += 11;
+            while (*request_buffer == ' ') request_buffer ++;
+
+            if (str_begin_with(request_buffer, "close"))
+                request->connection = WEBNET_CONN_CLOSE;
+            else if (str_begin_with(request_buffer, "Keep-Alive"))
+                request->connection = WEBNET_CONN_KEEPALIVE;
+        }
+#endif
+#ifdef WEBNET_USING_COOKIE
+        else if (str_begin_with(request_buffer, "Cookie:"))
+        {
+            /* get cookie */
+            request_buffer += 7;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->cookie = wn_strdup(request_buffer);
+        }
+#endif /* WEBNET_USING_COOKIE */
+#ifdef WEBNET_USING_AUTH
+        else if (str_begin_with(request_buffer, "Authorization: Basic"))
+        {
+            /* get authorization */
+            request_buffer += 20;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->authorization = wn_strdup(request_buffer);
+        }
+#endif /* WEBNET_USING_AUTH */
+#if WEBNET_CACHE_LEVEL > 0
+        else if (str_begin_with(request_buffer, "If-Modified-Since:"))
+        {
+            /* get If-Modified-Since */
+            request_buffer += 18;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->modified = wn_strdup(request_buffer);
+        }
+#endif /* WEBNET_CACHE_LEVEL > 0 */
+#ifdef WEBNET_USING_GZIP
+        else if (str_begin_with(request_buffer, "Accept-Encoding:"))
+        {
+            const char *gzip = strstr(request_buffer, "gzip");
+
+            if( (gzip != RT_NULL))
+            {
+                request->support_gzip = RT_TRUE;
+            }
+        }
+#endif /* WEBNET_USING_GZIP */
+        else if (str_begin_with(request_buffer, "SOAPACTION:"))
+        {
+            request_buffer += 11;
+            while (*request_buffer == ' ') request_buffer ++;
+
+            request->soap_action = wn_strdup(request_buffer);
+        }
+        else if (str_begin_with(request_buffer, "CALLBACK:"))
+        {
+            request_buffer += 9;
+            while (*request_buffer == ' ') request_buffer ++;
+
+            request->callback = wn_strdup(request_buffer);
+        }
+
+        request_buffer = ch;
+    }
+
+    return request_buffer - buffer;
+}
+
+int webnet_request_parse_post(struct webnet_request* request, char* buffer, int length)
+{
+    struct webnet_session* session = request->session;
+
+    if (request->query && length)
+    {
+        if (request->query_offset + length > request->content_length)
+            length = request->content_length - request->query_offset;
+
+        memcpy(&request->query[request->query_offset], buffer, length);
+        request->query_offset += length;
+
+        if (request->query_offset == request->content_length)
+        {
+            /* set terminal charater */
+            buffer[request->content_length] = '\0';
+
+            /* parse query */
+            if (str_begin_with(request->content_type, "application/x-www-form-urlencoded"))
+            {
+                _webnet_request_parse_query(request);
+            }
+
+            /* set to http response phase */
+            request->result_code = 200;
+            session->session_phase = WEB_PHASE_RESPONSE;
+        }
+    }
+
+    return length;
+}
+
+/**
+ * parse web request
+ */
+void webnet_request_parse(struct webnet_request* request, char* buffer, int length)
+{
+    char *ch;
+    char *request_buffer;
+    char *content_length;
+
+    RT_ASSERT(request != RT_NULL);
+    RT_ASSERT(request->session != RT_NULL);
+
+    content_length = RT_NULL;
+    request_buffer = buffer;
+    /* web request begin with method */
+    if (str_begin_with(request_buffer, "GET "))
+    {
+        request->method = WEBNET_GET;
+        request_buffer += 4;
+    }
+    else if (str_begin_with(request_buffer, "POST "))
+    {
+        request->method = WEBNET_POST;
+        request_buffer += 5;
+    }
+    else if (str_begin_with(request_buffer, "HEADER "))
+    {
+        request->method = WEBNET_HEADER;
+        request_buffer += 7;
+    }
+    else if (str_begin_with(request_buffer, "SUBSCRIBE "))
+    {
+        request->method = WEBNET_SUBSCRIBE;
+        request_buffer += 10;
+    }
+    else if (str_begin_with(request_buffer, "UNSUBSCRIBE "))
+    {
+        request->method = WEBNET_UNSUBSCRIBE;
+        request_buffer += 12;
+    }
+    else if (str_begin_with(request_buffer, "NOTIFY "))
+    {
+        request->method = WEBNET_NOTIFY;
+        request_buffer += 7;
+    }
+#ifdef WEBNET_USING_DAV
+    else if (str_begin_with(request_buffer, "PUT "))
+    {
+        request->method = WEBNET_PUT;
+        request_buffer += 4;
+    }
+    else if(str_begin_with(request_buffer, "OPTIONS "))
+    {
+        request->method = WEBNET_OPTIONS;
+        request_buffer += 8;
+    }
+    else if(str_begin_with(request_buffer, "PROPFIND "))
+    {
+        request->method = WEBNET_PROPFIND;
+        request_buffer += 9;
+    }
+    else if(str_begin_with(request_buffer, "PROPPATCH "))
+    {
+        request->method = WEBNET_PROPPATCH;
+        request_buffer += 10;
+    }
+    else if (str_begin_with(request_buffer, "DELETE "))
+    {
+        request->method = WEBNET_DELETE;
+        request_buffer += 7;
+    }
+    else if (str_begin_with(request_buffer, "MKCOL "))
+    {
+        request->method = WEBNET_MKCOL;
+        request_buffer += 6;
+    }
+    else if (str_begin_with(request_buffer, "HEAD "))
+    {
+        request->method = WEBNET_HEAD;
+        request_buffer += 5;
+    }
+#endif /* WEBNET_USING_DAV */
+    else
+    {
+        /* Not implemented for other method */
+        request->result_code = 501;
+        return ;
+    }
+
+    /* get path */
+    request->path = request_buffer;
+    ch = strchr(request_buffer, ' ');
+    if (ch == RT_NULL)
+    {
+        /* Bad Request */
+        request->result_code = 400;
+        return;
+    }
+    *ch++ = '\0';
+    request_buffer = ch;
+
+    /* check path, whether there is a query */
+    ch = strchr(request->path, '?');
+    if (ch != RT_NULL)
+    {
+        *ch++ = '\0';
+        while (*ch == ' ') ch ++;
+
+        /* copy query and parse query */
+        request->query = wn_strdup(ch);
+        /* copy query and parse parameter */
+        _webnet_request_parse_query(request);
+    }
+
+    /* check protocol */
+    if (!str_begin_with(request_buffer, "HTTP/1"))
+    {
+        /* Not implemented, webnet does not support HTTP 0.9 protocol */
+        request->result_code = 501;
+        return;
+    }
+
+    ch = strstr(request_buffer, "\r\n");
+    if (ch == RT_NULL)
+    {
+        /* Bad Request */
+        request->result_code = 400;
+        return;
+    }
+    *ch ++ = '\0';
+    *ch ++ = '\0';
+    request_buffer = ch;
+
+    for (;;)
+    {
+        if (str_begin_with(request_buffer, "\r\n"))
+        {
+            /* end of get request */
+
+            /* made a string field copy */
+            _webnet_request_copy_str(request);
+
+            /* get content length */
+            if (content_length != RT_NULL)
+                request->content_length = atoi(content_length);
+
+            if (request->method == WEBNET_POST && request->content_length > 0) /* POST method */
+            {
+                char *read_ptr = RT_NULL;
+                int read_length;
+                struct webnet_session* session = request->session;
+
+                /* skip '\r\n' */
+                request->query = request_buffer + 2;
+
+                /* check whether it's an uploading request */
+                if (str_begin_with(request->content_type, "multipart/form-data"))
+                {
+                    /* end of http request */
+                    request->result_code = 200;
+                    session->buffer_offset = (rt_uint16_t)(length -
+                                                      ((rt_uint32_t)request->query - (rt_uint32_t)buffer));
+                    /* move the buffer to the session */
+                    if (session->buffer_offset > 0)
+                    {
+                        /*
+                         * NOTIC: the rest of buffer must not be great than session buffer
+                         * Therefore, the size of read buffer can equal to the size of session buffer.
+                         */
+                        memcpy(session->buffer, request->query, session->buffer_offset);
+                    }
+                    request->query = RT_NULL;
+                    return;
+                }
+
+                if (request->content_length > POST_BUFSZ_MAX)
+                {
+                    LOG_E("content length too long: %d, discard it.", request->content_length);
+                    /* we can not provide enough buffer for post */
+                    request->query = RT_NULL;
+                    return;
+                }
+
+                /* allocate a new query content and copy the already read content */
+                read_ptr = (char *)wn_malloc(request->content_length);
+                if (read_ptr == RT_NULL)
+                {
+                    LOG_E("No memory for request read buffer!");
+                    request->query = RT_NULL;
+                    return ;
+                }
+
+                /* get the length of already read bytes */
+                read_length = (int)(length - ((rt_uint32_t)request->query - (rt_uint32_t)buffer));
+                if (read_length > 0)
+                {
+                    if (read_length > request->content_length) read_length = request->content_length;
+                    memcpy(read_ptr, request->query, read_length);
+                }
+                request->query = read_ptr;
+
+                read_ptr += read_length;
+                /* read all of post content */
+                while (read_length < request->content_length)
+                {
+                    int read_bytes;
+
+                    /* read the rest content of POST */
+                    read_bytes = webnet_session_read(session, read_ptr, request->content_length - read_length);
+                    if (read_bytes < 0)
+                    {
+                        /* read failed and session should been closed. */
+                        wn_free(request->query);
+                        request->query = RT_NULL;
+                        return;
+                    }
+
+                    read_length += read_bytes;
+                    read_ptr += read_bytes;
+                }
+
+                session->buffer_offset = request->content_length;
+                if (!str_begin_with(request->content_type, "text/xml"))
+                    _webnet_request_parse_query(request);
+                else
+                {
+                    /* do other POST action */
+                }
+            }
+
+            /* end of http request */
+            request->result_code = 200;
+
+            return;
+        }
+
+        if (*request_buffer == '\0')
+        {
+            /* end of http request */
+            request->result_code = 200;
+
+            /* made a string field copy */
+            _webnet_request_copy_str(request);
+            return;
+        }
+
+        if (str_begin_with(request_buffer, "Host:"))
+        {
+            /* get host */
+            request_buffer += 5;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->host = request_buffer;
+        }
+        else if (str_begin_with(request_buffer, "User-Agent:"))
+        {
+            /* get user agent */
+            request_buffer += 11;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->user_agent = request_buffer;
+        }
+        else if (str_begin_with(request_buffer, "Accept-Language:"))
+        {
+            /* get accept language */
+            request_buffer += 16;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->accept_language = request_buffer;
+        }
+        else if (str_begin_with(request_buffer, "Content-Length:"))
+        {
+            /* get content length */
+            request_buffer += 15;
+            while (*request_buffer == ' ') request_buffer ++;
+            content_length = request_buffer;
+        }
+        else if (str_begin_with(request_buffer, "Content-Type:"))
+        {
+            /* get content type */
+            request_buffer += 13;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->content_type = request_buffer;
+        }
+        else if (str_begin_with(request_buffer, "Referer:"))
+        {
+            /* get referer */
+            request_buffer += 8;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->referer = request_buffer;
+        }
+#ifdef WEBNET_USING_DAV
+        else if(str_begin_with(request_buffer, "Depth:"))
+        {
+            request_buffer += 6;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->depth = request_buffer;
+        }
+#endif /* WEBNET_USING_DAV */
+#ifdef WEBNET_USING_KEEPALIVE
+        else if (str_begin_with(request_buffer, "Connection:"))
+        {
+            /* set default connection to keep alive */
+            request->connection = WEBNET_CONN_KEEPALIVE;
+
+            /* get connection */
+            request_buffer += 11;
+            while (*request_buffer == ' ') request_buffer ++;
+
+            if (str_begin_with(request_buffer, "close"))
+                request->connection = WEBNET_CONN_CLOSE;
+            else if (str_begin_with(request_buffer, "Keep-Alive"))
+                request->connection = WEBNET_CONN_KEEPALIVE;
+        }
+#endif
+#ifdef WEBNET_USING_COOKIE
+        else if (str_begin_with(request_buffer, "Cookie:"))
+        {
+            /* get cookie */
+            request_buffer += 7;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->cookie = request_buffer;
+        }
+#endif /* WEBNET_USING_COOKIE */
+#ifdef WEBNET_USING_AUTH
+        else if (str_begin_with(request_buffer, "Authorization: Basic"))
+        {
+            /* get authorization */
+            request_buffer += 20;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->authorization = request_buffer;
+        }
+#endif /* WEBNET_USING_AUTH */
+#if WEBNET_CACHE_LEVEL > 0
+        else if (str_begin_with(request_buffer, "If-Modified-Since:"))
+        {
+            /* get If-Modified-Since */
+            request_buffer += 18;
+            while (*request_buffer == ' ') request_buffer ++;
+            request->modified = request_buffer;
+        }
+#endif /* WEBNET_CACHE_LEVEL > 0 */
+#ifdef WEBNET_USING_GZIP
+        else if (str_begin_with(request_buffer, "Accept-Encoding:"))
+        {
+            const char *end = strstr(request_buffer, "\r\n");
+            const char *gzip = strstr(request_buffer, "gzip");
+
+            if( (gzip != RT_NULL) && (end != RT_NULL) && (gzip < end) )
+            {
+                request->support_gzip = RT_TRUE;
+            }
+        }
+#endif /* WEBNET_USING_GZIP */
+        else if (str_begin_with(request_buffer, "SOAPACTION:"))
+        {
+            request_buffer += 11;
+            while (*request_buffer == ' ') request_buffer ++;
+
+            request->soap_action = request_buffer;
+        }
+        else if (str_begin_with(request_buffer, "CALLBACK:"))
+        {
+            request_buffer += 9;
+            while (*request_buffer == ' ') request_buffer ++;
+
+            request->callback = request_buffer;
+        }
+
+        ch = strstr(request_buffer, "\r\n");
+        if (ch == RT_NULL)
+        {
+            /* Bad Request */
+            request->result_code = 400;
+            return;
+        }
+        /* terminal field */
+        *ch ++ = '\0';
+        *ch ++ = '\0';
+
+        request_buffer = ch;
+    }
+}
+
+struct webnet_request* webnet_request_create()
+{
+    struct webnet_request* request;
+
+    request = (struct webnet_request*) wn_malloc (sizeof(struct webnet_request));
+    if (request != RT_NULL)
+    {
+        rt_memset(request, 0, sizeof(struct webnet_request));
+        request->field_copied = RT_FALSE;
+    }
+
+    return request;
+}
+
+void webnet_request_destory(struct webnet_request* request)
+{
+    if (request != RT_NULL)
+    {
+        // if (request->field_copied == RT_TRUE)
+        {
+            if (request->path != RT_NULL) wn_free(request->path);
+            if (request->host != RT_NULL) wn_free(request->host);
+            if (request->cookie != RT_NULL) wn_free(request->cookie);
+            if (request->user_agent != RT_NULL) wn_free(request->user_agent);
+            if (request->authorization != RT_NULL) wn_free(request->authorization);
+            if (request->accept_language != RT_NULL) wn_free(request->accept_language);
+            if (request->referer != RT_NULL) wn_free(request->referer);
+            if (request->content_type != RT_NULL) wn_free(request->content_type);
+            if (request->query != RT_NULL) wn_free(request->query);
+            if (request->query_items != RT_NULL) wn_free(request->query_items);
+
+            if (request->callback) wn_free(request->callback);
+            if (request->soap_action) wn_free(request->soap_action);
+            if (request->sid) wn_free(request->sid);
+#ifdef WEBNET_USING_RANGE
+            if (request->Range) wn_free(request->Range);
+#endif /* WEBNET_USING_RANGE */
+#ifdef WEBNET_USING_DAV
+            if (request->depth) wn_free(request->depth);
+#endif
+        }
+
+        /* free request memory block */
+        wn_free(request);
+    }
+}

+ 619 - 0
src/wn_session.c

@@ -0,0 +1,619 @@
+/*
+ * File      : wn_session.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ * 2012-06-25     Bernard      fixed the close session issue.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <webnet.h>
+#include <wn_module.h>
+#include <wn_utils.h>
+
+static struct webnet_session *_session_list = 0;
+
+/**
+ * create a webnet session
+ *
+ * @param listenfd, the listen file descriptor
+ *
+ * @return the created web session
+ */
+struct webnet_session* webnet_session_create(int listenfd)
+{
+    struct webnet_session* session;
+
+    /* create a new session */
+    session = (struct webnet_session *)wn_malloc(sizeof(struct webnet_session));
+    if (session != RT_NULL)
+    {
+        socklen_t clilen;
+		
+        memset(session, 0x0, sizeof(struct webnet_session));
+        session->session_ops = RT_NULL;
+
+        clilen = sizeof(struct sockaddr_in);
+        session->socket = accept(listenfd, (struct sockaddr *) &(session->cliaddr), &clilen);
+        if (session->socket < 0)
+        {
+            wn_free(session);
+            session = RT_NULL;
+
+            return session;
+        }
+        else
+        {
+            /* keep this session in our list */
+            session->next = _session_list;
+            _session_list = session;
+        }
+
+        /* initial buffer length */
+        session->buffer_length = WEBNET_SESSION_BUFSZ;
+    }
+
+    return session;
+}
+
+/**
+ * read data from a webnet session
+ *
+ * @param session, the web session
+ * @param buffer, the buffer to save read data
+ * @param length, the maximal length of data buffer to save read data
+ *
+ * @return the number of bytes actually read data
+ */
+int webnet_session_read(struct webnet_session *session, char *buffer, int length)
+{
+    int read_count;
+
+    /* Read some data */
+    read_count = recvfrom(session->socket, buffer, length, 0, NULL, NULL);
+    if (read_count <= 0)
+    {
+        session->session_phase = WEB_PHASE_CLOSE;
+        return -1;
+    }
+
+    return read_count;
+}
+RTM_EXPORT(webnet_session_read);
+
+/**
+ * close a webnet session
+ *
+ * @param session, the web session
+ */
+void webnet_session_close(struct webnet_session *session)
+{
+    struct webnet_session *iter;
+
+    /* invoke session close */
+    if (session->session_ops != RT_NULL &&
+            session->session_ops->session_close != RT_NULL)
+    {
+        session->session_ops->session_close(session);
+    }
+
+    /* Either an error or tcp connection closed on other
+     * end. Close here */
+    closesocket(session->socket);
+
+    /* Free webnet_session */
+    if (_session_list == session)
+        _session_list = session->next;
+    else
+    {
+        for (iter = _session_list; iter; iter = iter->next)
+        {
+            if (iter->next == session)
+            {
+                iter->next = session->next;
+                break;
+            }
+        }
+    }
+
+    if (session->request != RT_NULL)
+    {
+        webnet_request_destory(session->request);
+        session->request = RT_NULL;
+    }
+
+    wn_free(session);
+}
+
+/**
+ * print formatted data to session
+ *
+ * @param session, the web session
+ * @param fmt, the format string
+ */
+void webnet_session_printf(struct webnet_session *session, const char* fmt, ...)
+{
+    va_list args;
+    rt_uint32_t length;
+
+    va_start(args, fmt);
+    length = vsnprintf((char*)(session->buffer),
+                       sizeof(session->buffer) - 1,
+                       fmt, args);
+    session->buffer[length] = '\0';
+    va_end(args);
+
+    send(session->socket, session->buffer, length, 0);
+}
+RTM_EXPORT(webnet_session_printf);
+
+/**
+ * write data to session
+ *
+ * @param session, the web session
+ * @param data, the data will be write to the session
+ * @param size, the size of data bytes
+ *
+ * @return the number of bytes actually written to the session
+ */
+int webnet_session_write(struct webnet_session *session, const rt_uint8_t* data, rt_size_t size)
+{
+    /* send data directly */
+    send(session->socket, data, size, 0);
+
+    return size;
+}
+RTM_EXPORT(webnet_session_write);
+
+/**
+ * redirect to another local URL
+ *
+ * @param session, the web session
+ * @param url, the new URL link on the local
+ *
+ * @return
+ */
+int webnet_session_redirect(struct webnet_session *session, const char* url)
+{
+    struct webnet_request *request;
+
+    RT_ASSERT(session != RT_NULL);
+    request = session->request;
+    RT_ASSERT(request != RT_NULL);
+
+    /* change the request path to URL */
+    if (request->path != RT_NULL)
+        wn_free(request->path);
+    request->path = wn_strdup(url);
+
+    /* handle this URL */
+    return webnet_module_handle_uri(session);
+}
+RTM_EXPORT(webnet_session_redirect);
+
+/**
+ * Get physical path according to a virtual path
+ *
+ * @param session, the webnet session
+ * @param virtual_path, the virtual path
+ * @param full_path, the output full path
+ *
+ * @return 0 on convert successfull.
+ *
+ * NOTE: the length of full path is WEBNET_PATH_MAX */
+int webnet_session_get_physical_path(struct webnet_session *session, const char* virtual_path, char* full_path)
+{
+    int result;
+
+    result = 0;
+    if (full_path == RT_NULL) return -1;
+
+    /* made a full path */
+    rt_snprintf(full_path, WEBNET_PATH_MAX, "%s/%s", webnet_get_root(), virtual_path);
+    /* normalize path */
+    str_normalize_path(full_path);
+
+    /* check URI valid */
+    if (!str_begin_with(full_path, webnet_get_root()))
+    {
+        /* not found */
+        result = -1;
+    }
+
+    return result;
+}
+RTM_EXPORT(webnet_session_get_physical_path);
+
+/**
+ * set the http response header field Status-Line.
+ *
+ * @param session the web session
+ * @param code the http response code
+ * @param reason_phrase reason phrase string
+ */
+void webnet_session_set_header_status_line(struct webnet_session *session,
+        int code,
+        const char * reason_phrase)
+{
+    char status_line[16]; /* "HTTP/1.1 ### " */
+
+    rt_snprintf(status_line, sizeof(status_line) - 1, "HTTP/1.1 %d ", code);
+    webnet_session_printf(session, status_line);
+    webnet_session_printf(session, reason_phrase);
+    webnet_session_printf(session, "\r\n");
+    webnet_session_printf(session, WEBNET_SERVER);
+}
+RTM_EXPORT(webnet_session_set_header_status_line);
+
+/**
+ * set the http response header
+ *
+ * @param session the web session
+ * @param mimetype the mime type of http response
+ * @param code the http response code
+ * @param title the code title string
+ * @param length the length of http response content
+ */
+void webnet_session_set_header(struct webnet_session *session, const char* mimetype, int code, const char* title, int length)
+{
+    static const char* fmt = "HTTP/1.1 %d %s\r\n%s";
+    static const char* content = "Content-Type: %s\r\nContent-Length: %ld\r\nConnection: %s\r\n\r\n";
+    static const char* content_nolength = "Content-Type: %s\r\nConnection: %s\r\n\r\n";
+    static const char* auth = "WWW-Authenticate: Basic realm=%s\r\n";
+
+    char *ptr, *end_buffer;
+    int offset;
+
+    ptr = (char*)session->buffer;
+    end_buffer = (char*)session->buffer + session->buffer_length;
+
+    offset = rt_snprintf(ptr, end_buffer - ptr, fmt, code, title, WEBNET_SERVER);
+    ptr += offset;
+
+    if (code == 401)
+    {
+        offset = rt_snprintf(ptr, end_buffer - ptr, auth, session->request->host);
+        ptr += offset;
+    }
+    if (length >= 0)
+    {
+        offset = rt_snprintf(ptr, end_buffer - ptr, content, mimetype, length,
+                             session->request->connection == WEBNET_CONN_CLOSE? "close" : "Keep-Alive");
+        ptr += offset;
+    }
+    else
+    {
+        offset = rt_snprintf(ptr, end_buffer - ptr, content_nolength, mimetype, "close");
+        ptr += offset;
+    }
+    /* get the total length */
+    length = ptr - (char*)session->buffer;
+	
+    /* invoke webnet event */
+    if (webnet_module_handle_event(session, WEBNET_EVENT_RSP_HEADER) == WEBNET_MODULE_CONTINUE)
+    {
+        /* write to session */
+        webnet_session_write(session, session->buffer, length);
+    }
+}
+RTM_EXPORT(webnet_session_set_header);
+
+static void _webnet_session_handle_read(struct webnet_session* session)
+{
+    int read_length;
+    rt_uint8_t *buffer_ptr;
+
+    buffer_ptr = &session->buffer[session->buffer_offset];
+    /* to read data from the socket */
+    read_length = webnet_session_read(session, (char*)buffer_ptr, session->buffer_length - session->buffer_offset);
+	
+    if (read_length > 0) session->buffer_offset += read_length;
+
+    if (session->buffer_offset)
+    {
+        /* parse web method phase */
+        if (session->session_phase == WEB_PHASE_METHOD)
+        {
+            int length = webnet_request_parse_method(session->request, (char*)&session->buffer[0],
+                session->buffer_offset);
+
+            if (length)
+            {
+                if (length < session->buffer_offset)
+                {
+                    session->buffer_offset -= length;
+                    /* move to the begining of buffer */
+                    memmove(session->buffer, &session->buffer[length], session->buffer_offset);
+                }
+                else session->buffer_offset = 0;
+            }
+        }
+
+        /* parse web request header phase */
+        if (session->session_phase == WEB_PHASE_HEADER)
+        {
+            int length = webnet_request_parse_header(session->request, (char*)&session->buffer[0],
+                session->buffer_offset);
+
+            if (length)
+            {
+                if (length < session->buffer_offset)
+                {
+                    session->buffer_offset -= length;
+                    /* move to the begining of buffer */
+                    memmove(session->buffer, &session->buffer[length], session->buffer_offset);
+                }
+                else session->buffer_offset = 0;
+            }
+        }
+
+        if (session->session_phase == WEB_PHASE_QUERY)
+        {
+            int length = webnet_request_parse_post(session->request, (char*)&session->buffer[0],
+                session->buffer_offset);
+
+            if (length)
+            {
+                if (length < session->buffer_offset)
+                {
+                    session->buffer_offset -= length;
+                    /* move to the begining of buffer */
+                    memmove(session->buffer, &session->buffer[length], session->buffer_offset);
+                }
+                else session->buffer_offset = 0;
+            }
+        }
+    }
+}
+
+static void _webnet_session_handle_write(struct webnet_session* session)
+{
+}
+
+static void _webnet_session_handle(struct webnet_session* session, int event)
+{
+    switch (event)
+    {
+    case WEBNET_EVENT_READ:
+        _webnet_session_handle_read(session);
+
+        /* in the response phase */
+        if (session->session_phase == WEB_PHASE_RESPONSE)
+        {
+            /* remove the default session ops */
+            session->session_ops = NULL;
+            session->user_data = 0;
+
+            /* to handle response, then let module to handle url */
+            webnet_module_handle_uri(session);
+        }
+        break;
+
+    case WEBNET_EVENT_WRITE:
+        _webnet_session_handle_write(session);
+        break;
+    }
+}
+
+const struct webnet_session_ops _default_session_ops =
+{
+    _webnet_session_handle,
+    RT_NULL
+};
+
+static void _webnet_session_badrequest(struct webnet_session *session, int code)
+{
+    const char* title;
+    static const char* fmt = "<html><head><title>%d %s</title></head><body>%d %s</body></html>\r\n";
+
+    title = "Unknown";
+    switch (code)
+    {
+    case 304:
+        title = "Not Modified";
+        break;
+    case 400:
+        title = "Bad Request";
+        break;
+    case 401:
+        title = "Authorization Required";
+        break;
+    case 403:
+        title = "Forbidden";
+        break;
+    case 404:
+        title = "Not Found";
+        break;
+    case 405:
+        title = "Method Not Allowed";
+        break;
+    case 500:
+        title = "Internal Server Error";
+        break;
+    case 501:
+        title = "Not Implemented";
+        break;
+    case 505:
+        title = "HTTP Version Not Supported";
+        break;
+    }
+#ifdef WEBNET_USING_KEEPALIVE
+    if (code >= 400)
+    {
+        session->request->connection = WEBNET_CONN_CLOSE;
+    }
+#endif
+
+    webnet_session_set_header(session, "text/html", code, title, -1);
+    webnet_session_printf(session, fmt, code, title, code, title);
+}
+
+/**
+ * set the file descriptors
+ *
+ * @param readset, the file descriptors set for read
+ * @param writeset, the file descriptors set for write
+ *
+ * @return the maximal file descriptor
+ */
+int webnet_sessions_set_fds(fd_set *readset, fd_set *writeset)
+{
+    int maxfdp1 = 0;
+    struct webnet_session *session;
+
+    for (session = _session_list; session; session = session->next)
+    {
+        if (maxfdp1 < session->socket + 1)
+            maxfdp1 = session->socket + 1;
+
+        FD_SET(session->socket, readset);
+        if (session->session_event_mask & WEBNET_EVENT_WRITE)
+            FD_SET(session->socket, writeset);
+    }
+
+    return maxfdp1;
+}
+
+/**
+ * handle the file descriptors request
+ *
+ * @param readset, the file descriptors set for read
+ * @param writeset, the file descriptors set for write
+ */
+void webnet_sessions_handle_fds(fd_set *readset, fd_set *writeset)
+{
+    struct webnet_session *session, *next_session;
+
+    /* Go through list of connected session and process data */
+    for (session = _session_list; session; session = next_session)
+    {
+        /* get next session firstly if this session is closed */
+        next_session = session->next;
+
+        if (FD_ISSET(session->socket, readset))
+        {
+            if (session->session_ops == RT_NULL)
+            {
+                struct webnet_request *request;
+
+                /* destroy old request */
+                if (session->request != RT_NULL)
+                {
+                    webnet_request_destory(session->request);
+                    session->request = RT_NULL;
+                }
+
+                /* create request and use the default session ops */
+                request = webnet_request_create();
+                if (request)
+                {
+                    session->request = request;
+                    session->session_phase = WEB_PHASE_METHOD;
+                    /* set the default session ops */
+                    session->session_ops = &_default_session_ops;
+                    session->user_data = RT_NULL;
+
+                    request->session = session;
+                    request->result_code = 200; /* set the default result code to 200 */
+
+                    /* handle read event */
+                    session->session_ops->session_handle(session, WEBNET_EVENT_READ);
+                }
+                else
+                {
+                    /* no memory, close this session */
+                    session->session_phase = WEB_PHASE_CLOSE;
+                }
+            }
+            else
+            {
+                if (session->session_ops->session_handle)
+                    session->session_ops->session_handle(session, WEBNET_EVENT_READ);
+            }
+
+            /* whether close this session */
+            if (session->session_ops == RT_NULL || session->session_phase == WEB_PHASE_CLOSE)
+            {
+                /* check result code */
+                if (session->request->result_code != 200)
+                {
+                    _webnet_session_badrequest(session, session->request->result_code);
+                }
+				
+                /* close this session */
+                webnet_session_close(session);
+            }
+        }
+        else if (FD_ISSET(session->socket, writeset))
+        {
+            /* handle for write fd set */
+            if (session->session_ops != RT_NULL &&
+                session->session_ops->session_handle != RT_NULL)
+            {
+                session->session_ops->session_handle(session, WEBNET_EVENT_WRITE);
+            }
+
+            /* whether close this session */
+            if (session->session_ops == RT_NULL || session->session_phase == WEB_PHASE_CLOSE)
+            {	
+                /* close this session */
+                webnet_session_close(session);
+            }
+        }
+    }
+}
+
+#ifdef RT_USING_FINSH
+#include <finsh.h>
+
+static void list_webnet(void)
+{
+    struct webnet_session *session;
+    char client_ip_str[16]; /* ###.###.###.### */
+    rt_uint32_t num = 0;
+
+    rt_enter_critical();
+    for (session = _session_list; session != RT_NULL; session = session->next)
+    {
+        strcpy(client_ip_str,
+               inet_ntoa(*((struct in_addr*)&(session->cliaddr.sin_addr))));
+
+        rt_kprintf("#%u client %s:%u \n",
+                   num++,
+                   client_ip_str,
+                   ntohs(session->cliaddr.sin_port));
+
+        if (session->request != RT_NULL)
+        {
+            rt_kprintf("path: %s\n", session->request->path);
+        }
+
+        rt_kprintf("\r\n");
+    }
+    rt_exit_critical();
+}
+FINSH_FUNCTION_EXPORT(list_webnet, list webnet session);
+#ifdef FINSH_USING_MSH
+MSH_CMD_EXPORT(list_webnet, list webnet session);
+#endif
+#endif /* RT_USING_FINSH */
+

+ 340 - 0
src/wn_utils.c

@@ -0,0 +1,340 @@
+/*
+ * File      : wn_utils.c
+ * This file is part of RT-Thread RTOS
+ * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team
+ *
+ * This software is dual-licensed: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation. For the terms of this
+ * license, see <http://www.gnu.org/licenses/>.
+ *
+ * You are free to use this software under the terms of the GNU General
+ * Public License, but WITHOUT ANY WARRANTY; without even the implied
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * Alternatively for commercial application, you can contact us
+ * by email <business@rt-thread.com> for commercial license.
+ *
+ * Change Logs:
+ * Date           Author       Notes
+ * 2011-08-02     Bernard      the first version
+ */
+
+#include <ctype.h>
+#include <rtthread.h>
+
+#include <webnet.h>
+#include <wn_utils.h>
+
+rt_inline int tohex(char c)
+{
+    if (c >= '0' && c <= '9')
+        return c - '0';
+    if (c >= 'a' && c <= 'f')
+        return c - 'a' + 10;
+    if (c >= 'A' && c <= 'F')
+        return c - 'A' + 10;
+    return -1;
+}
+
+int str_path_with(const char *s, const char *t)
+{
+    if (strncasecmp(s, t, strlen(t)) == 0
+            && (strlen(s) == strlen(t) || *(s + strlen(t)) == '/')) return 1;
+
+    return 0;
+}
+
+int str_begin_with(const char *s, const char *t)
+{
+    if (strncasecmp(s, t, strlen(t)) == 0) return 1;
+
+    return 0;
+}
+
+int str_end_with(const char* s, const char* t)
+{
+    const char* se;
+    register int s_len, t_len;
+
+    s_len = strlen(s);
+    t_len = strlen(t);
+
+    if (s_len < t_len) return 0;
+
+    se = s + s_len - t_len;
+    if (strncasecmp(se, t, t_len) == 0) return 1;
+
+    return 0;
+}
+
+char *str_decode_path(char *path)
+{
+    int x1;
+    int x2;
+    char *src = path;
+    char *dst = path;
+    char last = *path;
+
+    if (last != '/')
+        return RT_NULL;
+
+    while (*++src)
+    {
+        if (*src == '%' &&
+                (x1 = tohex(*(src + 1))) >= 0 &&
+                (x2 = tohex(*(src + 2))) >= 0)
+        {
+            src += 2;
+            if ((*src = x1 * 16 + x2) == 0) break;
+        }
+
+        if (*src == '\\') *src = '/';
+        if ((last != '.' && last != '/') || (*src != '.' && *src != '/'))
+            *dst++ = last = *src;
+    }
+
+    *dst = 0;
+
+    return path;
+}
+
+static const unsigned char base64_table[65] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+char *str_base64_encode(const char* src)
+{
+    unsigned char *out, *pos;
+    const unsigned char *end, *in;
+    size_t olen;
+    int len;
+
+    len = strlen(src);
+    olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */
+    olen += olen / 72; /* line feeds */
+    olen++; /* nul termination */
+
+    out = (unsigned char*)wn_malloc(olen);
+    if (out == RT_NULL) return RT_NULL;
+
+    end = (const unsigned char*)src + len;
+    in = (const unsigned char*)src;
+    pos = out;
+    while (end - in >= 3)
+    {
+        *pos++ = base64_table[in[0] >> 2];
+        *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)];
+        *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)];
+        *pos++ = base64_table[in[2] & 0x3f];
+        in += 3;
+    }
+
+    if (end - in)
+    {
+        *pos++ = base64_table[in[0] >> 2];
+
+        if (end - in == 1)
+        {
+            *pos++ = base64_table[(in[0] & 0x03) << 4];
+            *pos++ = '=';
+        }
+        else
+        {
+            *pos++ = base64_table[((in[0] & 0x03) << 4) |
+                                  (in[1] >> 4)];
+            *pos++ = base64_table[(in[1] & 0x0f) << 2];
+        }
+        *pos++ = '=';
+    }
+
+    *pos = '\0';
+    return (char*)out;
+}
+
+char* str_normalize_path(char* fullpath)
+{
+    char *dst0, *dst, *src;
+
+    src = fullpath;
+    dst = fullpath;
+
+    dst0 = dst;
+    while (1)
+    {
+        char c = *src;
+
+        if (c == '.')
+        {
+            if (!src[1]) src ++; /* '.' and ends */
+            else if (src[1] == '/')
+            {
+                /* './' case */
+                src += 2;
+
+                while ((*src == '/') && (*src != '\0')) src ++;
+                continue;
+            }
+            else if (src[1] == '.')
+            {
+                if (!src[2])
+                {
+                    /* '..' and ends case */
+                    src += 2;
+                    goto up_one;
+                }
+                else if (src[2] == '/')
+                {
+                    /* '../' case */
+                    src += 3;
+
+                    while ((*src == '/') && (*src != '\0')) src ++;
+                    goto up_one;
+                }
+            }
+        }
+
+        /* copy up the next '/' and erase all '/' */
+        while ((c = *src++) != '\0' && c != '/') *dst ++ = c;
+
+        if (c == '/')
+        {
+            *dst ++ = '/';
+            while (c == '/') c = *src++;
+
+            src --;
+        }
+        else if (!c) break;
+
+        continue;
+
+up_one:
+        dst --;
+        if (dst < dst0) return RT_NULL;
+        while (dst0 < dst && dst[-1] != '/') dst --;
+    }
+
+    *dst = '\0';
+
+    /* remove '/' in the end of path if exist */
+    dst --;
+    if ((dst != fullpath) && (*dst == '/')) *dst = '\0';
+
+    return fullpath;
+}
+
+char * urlencode(const char *str, int len, int *new_length)
+{
+    const char hexchars[] = "0123456789ABCDEF";
+
+    const char *from, *end;
+    const char *start;
+
+    char *to;
+    int c;
+
+    from = str;
+    end = str + len;
+    start = to = (char *) wn_malloc(3 * len + 1);
+    if(start == RT_NULL)
+    {
+        return RT_NULL;
+    }
+
+    while (from < end)
+    {
+        c = *from++;
+
+        if ( (c < '0' && c != '-' && c != '.')
+                 || (c == ' ')
+                 || (c < 'A' && c > '9')
+                 || (c > 'Z' && c < 'a' && c != '_')
+                 || (c > 'z') )
+        {
+            to[0] = '%';
+            to[1] = hexchars[c >> 4];
+            to[2] = hexchars[c & 15];
+            to += 3;
+        }
+        else
+        {
+            *to++ = c;
+        }
+    }
+
+    *to = 0;
+    if (new_length)
+    {
+        *new_length = to - start;
+    }
+
+    return (char *) start;
+}
+
+int urldecode(char *str, int len)
+{
+    char *dest = str;
+    char *data = str;
+
+    int value;
+    int c;
+
+    while (len--)
+    {
+        if (*data == '+')
+        {
+            *dest = ' ';
+        }
+        else if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1))
+                 && isxdigit((int) *(data + 2)))
+        {
+            c = ((unsigned char *)(data+1))[0];
+            if (isupper(c))
+                c = tolower(c);
+            value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16;
+
+            c = ((unsigned char *)(data+1))[1];
+            if (isupper(c))
+                c = tolower(c);
+            value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10;
+
+            *dest = (char)value ;
+            data += 2;
+            len -= 2;
+        }
+        else
+        {
+            *dest = *data;
+        }
+        data++;
+        dest++;
+    }
+    *dest = '\0';
+
+    return dest - str;
+}
+#ifdef _WIN32
+int  strncasecmp ( const char* s1, const char* s2, size_t len )
+{
+    register unsigned int  x2;
+    register unsigned int  x1;
+    register const char*   end = s1 + len;
+
+    while (1)
+    {
+        if ((s1 >= end) )
+            return 0;
+
+        x2 = *s2 - 'A'; if ((x2 < 26u)) x2 += 32;
+        x1 = *s1 - 'A'; if ((x1 < 26u)) x1 += 32;
+        s1++; s2++;
+
+        if ((x2 != x1))
+            break;
+
+        if ((x1 == (unsigned int)-'A'))
+            break;
+    }
+
+    return x1 - x2;
+}
+#endif