~lovesyao/+junk/transgs

« back to all changes in this revision

Viewing changes to nazo/http.d

  • Committer: Nazo
  • Date: 2008-10-18 08:26:14 UTC
  • Revision ID: lovesyao@hotmail.com-20081018082614-22qgtg2gsotz5r2i
initial checkin

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
module nazo.http;
 
2
/**
 
3
 * HttpのCgi向けAPI
 
4
 * History:
 
5
 *          0.0.0.5 色々改変
 
6
 *          0.0.0.4 basePathの追加
 
7
 *          0.0.0.3 高速化
 
8
 *          0.0.0.2 鯖上以外でインポートした時のバグ修正
 
9
 *          0.0.0.1 バージョン付け開始
 
10
 * Authors: Nazo
 
11
 * Version: 0.0.0.5
 
12
 * License: Public Domain
 
13
 */
 
14
import nazo.string, std.stdio, nazo.zlib, std.conv, std.regexp, nazo.savelist, nazo.stdio, nazo.env, nazo.log, std.uri, std.cstream, std.thread, std.conv;
 
15
 
 
16
/**
 
17
 * HTTPを扱うクラス
 
18
 * Bugs: multipart/form-dataはテストしてない。
 
19
 *       gzipに未対応
 
20
 *       port80以外やhttps使用時は未テスト(問題が起こる可能性が高い)
 
21
 */
 
22
class Http{
 
23
  public{
 
24
    enum methods{
 
25
      GET,
 
26
      POST,
 
27
      HEAD,
 
28
      OPTIONS,
 
29
      PUT,
 
30
      DELETE,
 
31
      TRACE,
 
32
      UNKNOWN,
 
33
    }
 
34
    private static string scriptname;
 
35
    private static string bin;
 
36
    static string getScriptName(){
 
37
      return scriptname;
 
38
    }
 
39
    private static bool isnph;
 
40
    static bool isNph(){
 
41
      return isnph;
 
42
    }
 
43
    static methods method;
 
44
    ///セキュリティーエラーを吐く
 
45
    static void securityError(){
 
46
      HttpResponse.write("Security Error!",403);
 
47
    }
 
48
    ///そのAcceptには対応してませんというエラーを吐く
 
49
    static void acceptError(){
 
50
      HttpResponse.write("そのAcceptには対応してませんよん",406);
 
51
    }
 
52
    ///リクエストがGETとPOST以外ならfalseを返す。(通常HEAD以外はありえないと思われ(PUTやDELETE、TARCEはApacheが防いでくれるとおも)。HEADは攻撃に役立つので防いどく)
 
53
    static bool isAllowMethod(){
 
54
      return cast(bool)(method==methods.POST||method==methods.GET);
 
55
    }
 
56
    ///GETならtrueを返す
 
57
    static bool isGetMethod(){
 
58
      return cast(bool)(method==methods.GET);
 
59
    }
 
60
    ///POSTならtrueを返す
 
61
    static bool isPostMethod(){
 
62
      return cast(bool)(method==methods.POST);
 
63
    }
 
64
    ///POST向けのこれ自身から呼び出されたかどうかをチェック
 
65
    static bool isValidReferer(){
 
66
      string referer=Env.TGet!("HTTP_REFERER")();
 
67
      if(referer==""){
 
68
        return false;
 
69
      }
 
70
      //pathinfoを考慮
 
71
      string schema=referer.getBefore(':');
 
72
      if(schema!="http"&&schema!="https")return false;
 
73
      referer=referer[schema.length+3..$];
 
74
      string refhost=getBefore(referer,'/');
 
75
      string tmp=getBefore(referer[refhost.length..$],'?');
 
76
      refhost=getBefore(refhost,':');//XXX 未テスト
 
77
      if(refhost==Env.TGet!("SERVER_NAME")())
 
78
        if(startWith(tmp,getScriptName))
 
79
          return true;
 
80
      return false;
 
81
    }
 
82
    ///Content-Typeのリストからどれを使うかを選ぶ
 
83
    ///Reterns: 何返していいか分からなかったら空文字列を返します。
 
84
    static string allowAccept(string[] lst){
 
85
      if(accept=="")return "";
 
86
      string[] acceptArray;
 
87
      getBefores(accept,',',delegate void(string i){acceptArray~=strip(getBefore(i,';'));});
 
88
      foreach(string type;lst){
 
89
        foreach(string i;acceptArray){
 
90
          if(i==type){
 
91
            return type;
 
92
          }
 
93
        }
 
94
      }
 
95
      return "";
 
96
    }
 
97
    ///Accept-Langageを返す
 
98
    ///Bugs: リストからどれを選ぶか方式にした方がいいが面倒臭い。
 
99
    static string acceptLanguage(){
 
100
      return acceptlanguage;
 
101
    }
 
102
/+    ///キャラクタリストからどれを使うかを選ぶ
 
103
    ///Bugs: Not Impl
 
104
    string acceptCharset(string[] lst){
 
105
      return this.Env.TGet!("HTTP_ACCEPT_CHARSET")();
 
106
    }
 
107
    ///エンコードのリストからどれを使うかを選ぶ
 
108
    ///Returns: lstの中に含まれていた場合はそれを、そうでない場合は""を返す。
 
109
    ///Bugs: Not Impl
 
110
    string acceptEncoding(string[] lst){
 
111
      return this.Env.TGet!("HTTP_ACCEPT_CHARSET")();
 
112
    }+/
 
113
    ///リクエストuriを取得
 
114
    static string reqURI(){
 
115
      return Env.TGet!("REQUEST_URI")();
 
116
    }
 
117
    private static string pathinfo;
 
118
    ///パスの情報を取得
 
119
    ///XXX: パスの正規化が必要かもしれない。
 
120
    static string pathInfo(){
 
121
      if(!pathinfo){
 
122
        pathinfo=Env.TGet!("PATH_INFO")();
 
123
//        RegExp re=new RegExp("(//|/./|/[^/]*/../)","g");
 
124
//        info=re.replace(info,"/");
 
125
      }
 
126
      return pathinfo;
 
127
    }
 
128
    ///スクリプトの絶対パス
 
129
    ///Bugs: ポートやhttps等に未対応
 
130
    static string path(){
 
131
      return "http://"~Env.TGet!("SERVER_NAME")()~getScriptName;
 
132
    }
 
133
    private static string relpath;
 
134
    ///スクリプトの相対パス
 
135
    static string relPath(){
 
136
      if(relpath==""){
 
137
/*        string dir;
 
138
        string path=pathInfo();
 
139
        if(path!=""){
 
140
          int len;
 
141
          getBeforesWithoutLast(path,'/',delegate void(string t){len+=3;});
 
142
          dir.length=len;
 
143
          for(int i;i<dir.length;i+=3){
 
144
            dir[i..i+3]="../";
 
145
          }
 
146
        }
 
147
        if(getScriptName=="")throw new Exception("環境変数 SCRIPT_NAME が空です。");*/
 
148
        relpath=basePath()~bin;
 
149
      }
 
150
      return relpath;
 
151
    }
 
152
    private static string basepath;
 
153
    ///スクリプトのベースパス
 
154
    static string basePath(){
 
155
      if(basepath==""){
 
156
        char[] dir;
 
157
        string path=pathInfo();
 
158
        if(path!=""){
 
159
          int len;
 
160
          getBeforesWithoutLast(path,'/',delegate void(string t){len+=3;});
 
161
          dir.length=len;
 
162
          for(int i;i<dir.length;i+=3){
 
163
            dir[i..i+3]="../"[];
 
164
          }
 
165
        }
 
166
        basepath=to!(string)(dir);
 
167
      }
 
168
      return basepath;
 
169
    }
 
170
    ///クッキーを取得
 
171
    static string cookie(){
 
172
      return Env.TGet!("HTTP_COOKIE")();
 
173
    }
 
174
    ///IPを取得
 
175
    static string ip(){
 
176
      return Env.TGet!("REMOTE_ADDR")();
 
177
    }
 
178
    ///ボット判定
 
179
    ///Safariが引っかかってるけど気にしない。
 
180
    static bool isBot(){
 
181
      string ua=Env.TGet!("HTTP_USER_AGENT")().tolower();
 
182
      if(ua=="")return true;
 
183
      int i;
 
184
      for(i=ua.length-3;i > ua.length-5;i--){
 
185
        if(ua[i]=='b'&&ua[i..i+3]=="bot")
 
186
          return true;
 
187
      }
 
188
      for(;i>=0;i--){
 
189
        switch(ua[i]){
 
190
          case 'b':
 
191
            if(ua[i..i+3]=="bot")
 
192
              return true;
 
193
            break;
 
194
          case 's':
 
195
            if(ua[i..i+5]=="slurp")
 
196
              return true;
 
197
            break;
 
198
          default:
 
199
        }
 
200
      }
 
201
//      if(ua==""||std.string.find(ua,"bot")!=-1||std.string.find(ua,"slurp")!=-1)return true;
 
202
      if(accept==""||accept=="*/*")return true;
 
203
      return false;
 
204
    }
 
205
    private static string accept;
 
206
    private static string acceptlanguage;
 
207
    ///ログ取り
 
208
    debug(HTTPLOG) static void writeLog(Log log,string flag){
 
209
      log.write(flag~":"~Env.get("REQUEST_METHOD")~"\t"~Env.get("SERVER_NAME")~getScriptName~Env.get("PATH_INFO")~"?"~Env.get("QUERY_STRING")~"\t"~Env.get("HTTP_USER_AGENT")~"\t"~Env.get("HTTP_ACCEPT")~"\t"~Env.get("HTTP_ACCEPT_LANGUAGE"));
 
210
    }
 
211
    ///クエリ
 
212
    static string[string] query;
 
213
    ///ファイル
 
214
    static File*[string] files;
 
215
    private this(){};
 
216
    private static Http m;
 
217
    ///インスタンスの取得(Singleton)
 
218
    static Http get(){
 
219
      synchronized{
 
220
        if(!m)m=new Http();
 
221
      }
 
222
      return m;
 
223
    }
 
224
    //HTTPを扱うのを作成するコンストラクタ
 
225
    static this(){
 
226
      scriptname=Env.TGet!("SCRIPT_NAME")();
 
227
      if(scriptname.length==0)return null;
 
228
      bin=getAfter(getScriptName,'/');
 
229
      isnph=startWith(bin,"nph-");
 
230
      string querystr;
 
231
      switch(Env.TGet!("REQUEST_METHOD")()){
 
232
        case "GET":
 
233
          querystr=Env.TGet!("QUERY_STRING")();
 
234
          method=methods.GET;
 
235
          break;
 
236
        case "POST":
 
237
          method=methods.POST;
 
238
          querystr=cast(string)StdIo.readStdIn(.toInt(Env.TGet!("CONTENT_LENGTH")()));
 
239
          break;
 
240
        case "HEAD":
 
241
          method=methods.HEAD;
 
242
          break;
 
243
        case "PUT":
 
244
          method=methods.PUT;
 
245
          break;
 
246
        case "DELETE":
 
247
          method=methods.DELETE;
 
248
          break;
 
249
        case "TRACE":
 
250
          method=methods.TRACE;
 
251
          break;
 
252
        default:
 
253
          method=methods.UNKNOWN;
 
254
      }
 
255
      accept=Env.TGet!("HTTP_ACCEPT")();
 
256
      acceptlanguage=Env.TGet!("HTTP_ACCEPT_LANGUAGE")();
 
257
      if(!startWith(strip(Env.TGet!("CONTENT_TYPE")()),"multipart/form-data")){
 
258
        getBefores(querystr,'&',delegate void(string i){
 
259
          int s=std.string.find(i,'=');
 
260
          if(s!=-1)Http.query[i[0..s]]=i[s+1..$];
 
261
        });
 
262
      }else{
 
263
        //XXX 未テスト。base64には対応していない。
 
264
        //filename='='の時問題あるかも
 
265
        uint start=std.string.find(Env.TGet!("CONTENT_TYPE")(),"boundary=")+"boundary=".length;
 
266
        string l="--"~Env.TGet!("CONTENT_TYPE")()[start..$];
 
267
        querystr=querystr[l.length+"\r\n".length..$-("--".length+l.length+"--".length)-"\r\n".length];
 
268
        string[] querylist=std.string.split(querystr,\r\n~l~\r\n);
 
269
        foreach(string i;querylist){
 
270
          string[] headers=std.string.split(getBefore(i,\r\n\r\n),\r\n);
 
271
          string[string] pref;
 
272
          foreach(string i2;headers){
 
273
            switch(getBefore(i2,':')){
 
274
              case "Content-Disposition":
 
275
                foreach(string i3;std.string.split(i2,";")){
 
276
                  string i4;
 
277
                  i4=strip(i3);
 
278
                  int t=std.string.find(i4,'=');
 
279
                  if(t==-1)continue;
 
280
                  pref[i4[0..t]]=i4[t+2..$-1];
 
281
                }
 
282
                break;
 
283
              default:
 
284
                break;
 
285
            }
 
286
          }
 
287
          if(auto name=("filename" in pref)){
 
288
            File* f=new File;
 
289
            f.name=*name;
 
290
            int s=std.string.find(i,\r\n\r\n);
 
291
            f.data=cast(ubyte[])i[s+4..$];
 
292
            files[pref["name"]]=f;
 
293
          }else{
 
294
            int s=std.string.find(i,\r\n\r\n);
 
295
            Http.query[pref["name"]]=i[s+4..$];
 
296
          }
 
297
        }
 
298
      }
 
299
    }
 
300
  }
 
301
}
 
302
 
 
303
struct File{
 
304
  string name;
 
305
  ubyte[] data;
 
306
}
 
307
 
 
308
///HTTPのレスポンスなクラス
 
309
class HttpResponse{
 
310
  public{
 
311
    ///書き込み
 
312
    static void write(string responseBody,uint status=200,string[string] headers=null){
 
313
      write(cast(ubyte[])responseBody,status,headers);
 
314
    }
 
315
    ///書き込み
 
316
    ///Bugs: gzipに未対応
 
317
    static void write(ubyte[] responseBody,uint status=200,string[string] headers=null){
 
318
      bool cmp=false;
 
319
      Thread ct;
 
320
      if(std.string.find(Env.TGet!("HTTP_ACCEPT_ENCODING")(),"deflate")!=-1){
 
321
        cmp=true;
 
322
        ct=new Thread(delegate int(){responseBody=cast(ubyte[])compress(responseBody);return 0;});
 
323
        ct.run;
 
324
      }
 
325
      bool isNph=Http.isNph;
 
326
      string header;
 
327
      if(isNph)header="HTTP/1.1 ";
 
328
      if(status!=200){
 
329
        if(!isNph)header="Status: ";
 
330
        if(status==304)
 
331
          header~="304 Not Modified\r\n";
 
332
        else if(status==403)
 
333
          header~="403 Forbidden\r\n";
 
334
        else if(status==404)
 
335
          header~="404 File Not Found\r\n";
 
336
        else if(status==406)
 
337
          header~="406 Not Acceptable\r\n";
 
338
        else if(status==301)
 
339
          header~="301 Moved Permanently\r\n";
 
340
      }else if(isNph)header~="200 OK\r\n";
 
341
      if(isNph)header~="Connection: Keep-Alive\r\nKeep-Alive: timeout=15, max=100\r\n";
 
342
      if(auto t="Vary" in headers){
 
343
        *t~=",Accept-Encoding";
 
344
      }else{
 
345
        header~="Vary: Accept-Encoding\r\n";
 
346
      }
 
347
      foreach(string key,inout string val;headers){
 
348
        header~=key~": "~val~"\r\n";
 
349
      }
 
350
      if(cmp){
 
351
        header~="Content-Encoding: deflate\r\n";
 
352
      }
 
353
      header~="\r\n";
 
354
      if(cmp){
 
355
        StdIo.writeStdOut(cast(ubyte[])header);
 
356
        ct.wait;
 
357
        StdIo.writeStdOutWithThread(responseBody);
 
358
      }else{
 
359
        StdIo.writeStdOutWithThread(cast(ubyte[])header~responseBody);
 
360
      }
 
361
    }
 
362
  }
 
363
}