birdpack 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/core.js ADDED
@@ -0,0 +1,411 @@
1
+ const tools = require('./tools');
2
+ const fs = require('fs');
3
+ const fileType = require('./fileType');
4
+
5
+ module.exports = class{
6
+ status = 0
7
+ header = {}
8
+ cookie = {}
9
+ buffers = []
10
+ body = {}
11
+ host = ''
12
+ url = ''
13
+ query = {}
14
+ method = ''
15
+ callrouter = ()=>{}
16
+ params = {}
17
+ constructor({req, res}){
18
+ this.req = req;
19
+ this.res = res;
20
+
21
+ this.req.on('data', chunk => {
22
+ this.buffers.push(chunk);
23
+ })
24
+ .on('end', () => {
25
+ if(this.buffers.length !== 0){
26
+ this.buffers = Buffer.concat(this.buffers);
27
+ }else{
28
+ this.buffers = Buffer.alloc(0);
29
+ }
30
+
31
+ if(this.process() !== false){
32
+ let type = this.get('content-type');
33
+ if(type !== false){
34
+ type = type.split('; ');
35
+
36
+ if(type[0] == 'application/x-www-form-urlencoded'){
37
+ this.body = tools.wwwform(this.buffers);
38
+ }else if(type[0] == 'application/json'){
39
+ try{
40
+ this.body = JSON.parse(this.buffers);
41
+ }catch(e){
42
+ this.error(500, 'Invalid JSON.');
43
+ return false;
44
+ }
45
+ }else if(type[0] == 'multipart/form-data' && type[1]){
46
+ let key = type[1].slice(9);
47
+ this.body = tools.formdata(this.buffers, key);
48
+ }
49
+ }
50
+ this.callrouter();
51
+ }
52
+ })
53
+ .on('error', (e) => {
54
+ console.log(e);
55
+ })
56
+ }
57
+ process(){
58
+ if(!this.req.headers.host){
59
+ this.error(400, 'Invalid Host.');
60
+ return false;
61
+ }
62
+
63
+ const findHost = this.req.headers.host.indexOf(':');
64
+ this.host = findHost === -1 ? this.req.headers.host : this.req.headers.host.slice(0, findHost);
65
+
66
+ this.method = this.req.method.toLowerCase();
67
+
68
+ const url = this.req.url.split('?');
69
+ this.path = url[0];
70
+ if(url[1]){
71
+ this.query = tools.wwwform(url[1]);
72
+ }
73
+
74
+ if(this.req.headers.hasOwnProperty('cookie')){
75
+ let cookie = [];
76
+ if(Array.isArray(this.req.headers.cookie)){
77
+ cookie = this.req.headers.cookie;
78
+ }else{
79
+ cookie.push(this.req.headers.cookie);
80
+ }
81
+
82
+ for(let x of cookie){
83
+ for(let i of x.split('; ')){
84
+ let index = i.indexOf('=');
85
+ let key = i.slice(0, index);
86
+ let value = i.slice(index + 1);
87
+
88
+ try{
89
+ key = decodeURIComponent(key);
90
+ value = decodeURIComponent(value);
91
+ }catch(e){}
92
+
93
+ this.cookie[key] = value;
94
+ }
95
+ }
96
+ }
97
+ return true;
98
+ }
99
+ router(call){
100
+ this.callrouter = call;
101
+ }
102
+
103
+ code(status){
104
+ this.status = status;
105
+ return this;
106
+ }
107
+ set(key, value){
108
+ if(this.header.hasOwnProperty(key)){
109
+ this.header[key].push(value);
110
+ }else{
111
+ this.header[key] = [value];
112
+ }
113
+ return this;
114
+ }
115
+ get(key){
116
+ if(this.req.headers.hasOwnProperty(key)){
117
+ let header = this.req.headers[key];
118
+ if(typeof header == 'string'){
119
+ return header;
120
+ }else if(Array.isArray(header)){
121
+ return header[header.length - 1];
122
+ }
123
+ }
124
+ return false;
125
+ }
126
+ param(key, value){
127
+ this.params[key] =value ;
128
+ return this;
129
+ }
130
+ look(keys){
131
+ for(let x of keys.split(/[\s]*,[\s]*/)){
132
+ if(!this.query.hasOwnProperty(x)){
133
+ return false;
134
+ }
135
+ }
136
+ return true;
137
+ }
138
+ u(key){
139
+ if(this.query.hasOwnProperty(key)){
140
+ if(Array.isArray(this.query[key])){
141
+ return this.query[key][this.query[key].length - 1];
142
+ }else{
143
+ return this.query[key];
144
+ }
145
+ }
146
+ return false;
147
+ }
148
+ scookie = (name, value, option) => {
149
+ let text = [`${name}=${encodeURI((value || '').toString())}`];
150
+ for(let x in option){
151
+ if(['domain', 'expires', 'httponly', 'max-age', 'partitioned', 'path', 'secure', 'sameSite'].includes(x)){
152
+ if(['string','number'].includes(typeof option[x])){
153
+ text.push(`${x}=${encodeURI(option[x].toString())}`);
154
+ }else{
155
+ text.push(`${x}`);
156
+ }
157
+ }
158
+ }
159
+ this.set('set-cookie', text.join('; '));
160
+ return this;
161
+ }
162
+ rcookie(name, option){
163
+ if(typeof option != 'object'){
164
+ option = {};
165
+ }
166
+ option['max-age'] = '-1';
167
+ this.scookie(name, '', option);
168
+ return this;
169
+ }
170
+
171
+ writeHead(){
172
+ this.header['x-powered-by'] = `R938`;
173
+ if(this.res.writable){
174
+ this.res.writeHead(this.status ? this.status : 200, this.header);
175
+ }else if(this.req.destroyed){
176
+ this.req.destroy();
177
+ }
178
+ return this;
179
+ }
180
+ write(data, callback){
181
+ if(this.res.writable){
182
+ this.res.write(data, callback);
183
+ }else if(this.req.destroyed){
184
+ this.req.destroy();
185
+ }
186
+ }
187
+ end(data, callback){
188
+ if(this.res.writable){
189
+ this.res.end(data, callback);
190
+ }else if(this.req.destroyed){
191
+ this.req.destroy();
192
+ }
193
+ }
194
+ error(status, text){
195
+ this.code(status).send(text || '');
196
+ }
197
+
198
+
199
+ length(length){
200
+ this.set('content-length', length);
201
+ return this;
202
+ }
203
+ type(type){
204
+ this.set('content-type', type);
205
+ return this;
206
+ }
207
+
208
+ check(keys){
209
+ for(let x of keys.split(/[\s]*,[\s]*/)){
210
+ if(!this.body.hasOwnProperty(x)){
211
+ return false;
212
+ }
213
+ }
214
+ return true;
215
+ }
216
+ q(key){
217
+ if(this.body.hasOwnProperty(key)){
218
+ if(Array.isArray(this.body[key])){
219
+ return this.body[key][this.body[key].length - 1];
220
+ }else{
221
+ return this.body[key];
222
+ }
223
+ }
224
+
225
+ return false;
226
+ }
227
+
228
+ send(data){
229
+ if(typeof data === 'string' || typeof data === 'number'){
230
+ data = Buffer.from(data.toString(), 'utf8');
231
+ }else if(!Buffer.isBuffer(data)){
232
+ data = Buffer.alloc(0);
233
+ }
234
+
235
+ if(!this.header.hasOwnProperty('content-type')){
236
+ this.type('text/plain; charset=UTF-8')
237
+ }
238
+ this.length(data.length);
239
+
240
+ this.writeHead();
241
+ this.end(data);
242
+ }
243
+ json(data){
244
+ try{
245
+ data = JSON.stringify(data);
246
+ this.type('application/json; charset=utf-8')
247
+ .send(data);
248
+ }catch(e){
249
+ this.error(500);
250
+ }
251
+ }
252
+ html(data){
253
+ this.type('text/html; charset=utf-8')
254
+ .send(data);
255
+ }
256
+
257
+ redirect(a, b){
258
+ const typeA = typeof a;
259
+ const typeB = typeof b;
260
+ let status = false;
261
+ let location = false;
262
+
263
+ if(typeA == 'number' && typeB == 'string'){
264
+ status = a;
265
+ location = b;
266
+ }else if(typeA == 'string'){
267
+ status = 301;
268
+ location = a;
269
+ }
270
+
271
+ if(status !== false){
272
+ this.code(status)
273
+ .set('location', location)
274
+ .writeHead();
275
+ this.end();
276
+ }else{
277
+ this.error(status);
278
+ }
279
+ }
280
+ file(options){
281
+ if(typeof options != 'object'){
282
+ options = {file: options};
283
+ }
284
+
285
+ if(typeof options.error !== 'function'){
286
+ options.error = (code, text)=>{
287
+ this.error(code, text);
288
+ }
289
+ }
290
+
291
+ const bufferSize = 64 * 1024;
292
+ const buffer = Buffer.alloc(bufferSize);
293
+ let fileStat = fs.existsSync(options.file) ? fs.statSync(options.file) : false;
294
+
295
+ if(fileStat !== false && fileStat.isFile()){
296
+ let fileExt = options.file.split('.').pop().toLowerCase();
297
+
298
+ let fileRange = 'file';
299
+ if(options.hasOwnProperty('type')){
300
+ }else if(fileType.hasOwnProperty(fileExt)){
301
+ if(options.download !== true && ['mp4','mp3','ogg','weba','webm','mp4','avi','wav'].includes(fileExt)){
302
+ fileRange = 'stream';
303
+ }
304
+ options.type = fileType[fileExt];
305
+ }
306
+
307
+ if(options.stream === true){
308
+ fileRange = 'stream';
309
+ }
310
+
311
+ if(fileRange == 'stream'){
312
+ const totalSize = fileStat.size;
313
+ let match = (this.get('range') || '').slice(6).split('-');
314
+
315
+ this.set('accept-ranges', 'bytes')
316
+ .type(options.type);
317
+
318
+ if(match.length == 2){
319
+ const start = parseInt(match[0], 10) || 0;
320
+ const end = parseInt(match[1], 10) || totalSize - 1;
321
+
322
+ const chunkSize = end - start + 1;
323
+ const fileDescriptor = fs.openSync(options.file, "r");
324
+
325
+ const sendChunk = (position) => {
326
+ if(position > end){
327
+ fs.closeSync(fileDescriptor);
328
+ return;
329
+ }
330
+
331
+ const bytesToRead = Math.min(bufferSize, end - position + 1);
332
+ const bytesRead = fs.readSync(fileDescriptor, buffer, 0, bytesToRead, position);
333
+
334
+ if(bytesRead > 0 && this.res.writable){
335
+ this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
336
+ if(err){
337
+ fs.closeSync(fileDescriptor);
338
+ }else{
339
+ sendChunk(position + bytesRead);
340
+ }
341
+ });
342
+ }else{
343
+ fs.closeSync(fileDescriptor);
344
+ }
345
+ };
346
+
347
+ this.code(206)
348
+ .length(chunkSize)
349
+ .set('content-range', `bytes ${start}-${end}/${totalSize}`)
350
+ .writeHead();
351
+ sendChunk(start);
352
+ }else{
353
+ const start = 0;
354
+ const end = totalSize - 1;
355
+ const chunkSize = end - start + 1;
356
+
357
+ this.code(200)
358
+ .length(chunkSize)
359
+ .set('content-range', `bytes ${start}-${end}/${totalSize}`)
360
+ .writeHead();
361
+ this.end();
362
+ }
363
+ }else if(fileRange == 'file'){
364
+ const totalSize = fileStat.size;
365
+
366
+ const start = 0;
367
+ const end = totalSize - 1;
368
+
369
+ const chunkSize = end - start + 1;
370
+ const fileDescriptor = fs.openSync(options.file, "r");
371
+
372
+ const sendChunk = (position) => {
373
+ if(position > end){
374
+ fs.closeSync(fileDescriptor);
375
+ return;
376
+ }
377
+
378
+ const bytesToRead = Math.min(bufferSize, end - position + 1);
379
+ const bytesRead = fs.readSync(fileDescriptor, buffer, 0, bytesToRead, position);
380
+
381
+ if(bytesRead > 0 && this.res.writable){
382
+ this[position + bytesRead > end ? 'end' : 'write'](buffer.slice(0, bytesRead), (err) => {
383
+ if (err) {
384
+ fs.closeSync(fileDescriptor);
385
+ } else {
386
+ sendChunk(position + bytesRead);
387
+ }
388
+ });
389
+ }else{
390
+ fs.closeSync(fileDescriptor);
391
+ }
392
+ };
393
+
394
+ this.code(200)
395
+ .length(chunkSize)
396
+ .type(options.type);
397
+
398
+ if(options.download === true){
399
+ this.set('content-disposition', `attachment; filename="${options.filename}"`);
400
+ }
401
+
402
+ this.writeHead();
403
+ sendChunk(start);
404
+ }else{
405
+ options.error(400, 'This file not supported.');
406
+ }
407
+ }else{
408
+ options.error(404, 'File not found.');
409
+ }
410
+ }
411
+ }
@@ -0,0 +1,35 @@
1
+ {
2
+ "html": "text/html; charset=UTF-8",
3
+ "txt": "text/plain; charset=UTF-8",
4
+ "json": "application/json; charset=UTF-8",
5
+ "gif": "image/gif",
6
+ "jpeg": "image/jpeg",
7
+ "jpg": "image/jpg",
8
+ "webp": "image/webp",
9
+ "png": "image/png",
10
+ "svg": "image/svg+xml",
11
+ "ico": "image/x-icon",
12
+ "css": "text/css; charset=UTF-8",
13
+ "js": "application/javascript; charset=UTF-8",
14
+ "xml": "application/xml; charset=UTF-8",
15
+ "ttf": "font/ttf",
16
+ "eot": "font/eot",
17
+ "otf": "font/otf",
18
+ "woff": "font/woff",
19
+ "woff2": "font/woff2",
20
+ "mp3": "audio/mpeg",
21
+ "ogg": "audio/ogg",
22
+ "wav": "audio/wav",
23
+ "weba": "audio/weba",
24
+ "webm": "video/webm",
25
+ "mp4": "video/mp4",
26
+ "ts": "video/MP2T",
27
+ "m3u8": "application/x-mpegURL",
28
+ "avi": "video/x-msvideo",
29
+ "pdf": "application/pdf",
30
+ "rar": "application/rar",
31
+ "zip": "application/zip",
32
+ "tar": "application/tar",
33
+ "docx": "application/docx",
34
+ "xlsx": "application/xlsx"
35
+ }