-
Notifications
You must be signed in to change notification settings - Fork 26
/
main.js
203 lines (176 loc) · 6.21 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
var fs = require('fs')
, util = require('util')
, crypto = require('crypto')
, stream = require('stream')
, path = require('path')
, mime = require('mime')
, rfc822 = require('./rfc822')
;
function File (options) {
stream.Stream.call(this)
this.writable = true
this.readable = true
this.buffers = []
var self = this
if (typeof options === 'string') options = {path:options}
if (!options.index) options.index = 'index.html'
self.writable = (typeof options.writable === "undefined") ? true : options.writable
self.readable = (typeof options.readable === "undefined") ? true : options.readable
self.path = options.path
self.index = options.index
self.on('pipe', function (src) {
this.src = src
})
this.buffering = true
this.mimetype = options.mimetype || mime.getType(this.path.slice(this.path.lastIndexOf('.')+1))
var stopBuffering = function () {
self.buffering = false
while (self.buffers.length) {
self.emit('data', self.buffers.shift())
}
if (self.ended) self.emit('end')
}
fs.stat(options.path, function (err, stats) {
var finish = function (err, stats) {
self.stat = stats
if (err && err.code === 'ENOENT' && !self.dest && !self.src) self.src = self.path
if (err && !self.dest && !self.src) return self.emit('error', err)
if (err && self.dest && !self.dest.writeHead) return self.emit('error', err)
// See if writes are disabled
if (self.src && self.src.method &&
!self.writable && self.dest.writeHead &&
(self.src.method === 'PUT' || self.src.method === 'POST')) {
self.dest.writeHead(405, {'content-type':'text/plain'})
self.dest.end(self.src.method+' Not Allowed')
return
}
if (!err) {
self.etag = crypto.createHash('md5').update(stats.ino+'/'+stats.mtime+'/'+stats.size).digest("hex")
self.lastmodified = rfc822.getRFC822Date(stats.mtime)
}
process.nextTick(function () {
stopBuffering()
})
// 404 and 500
if ( err && self.dest && self.dest.writeHead && // We have an error object and dest is an HTTP response
( // Either we have a source and it's a GET/HEAD or we don't have a src
(self.src && (self.src.method == 'GET' || self.src.method === 'HEAD')) || (!self.src)
)
) {
if (err.code === 'ENOENT') {
self.dest.statusCode = 404
self.dest.end('Not Found')
} else {
self.dest.statusCode = 500
self.dest.end(err.message)
}
return
}
// Source is an HTTP Server Request
if (self.src && (self.src.method === 'GET' || self.src.method === 'HEAD') && self.dest) {
if (self.dest.setHeader) {
self.dest.setHeader('content-type', self.mimetype)
self.dest.setHeader('etag', self.etag)
self.dest.setHeader('last-modified', self.lastmodified)
}
if (self.dest.writeHead) {
if (self.src && self.src.headers) {
if (self.src.headers['if-none-match'] === self.etag ||
// Lazy last-modifed matching but it's faster than parsing Datetime
self.src.headers['if-modified-since'] === self.lastmodified) {
self.dest.statusCode = 304
self.dest.end()
return
}
}
// We're going to return the whole file
self.dest.statusCode = 200
self.dest.setHeader('content-length', stats.size)
} else {
// Destination is not an HTTP response, GET and HEAD method are not allowed
return
}
if (self.src.method !== 'HEAD') {
fs.createReadStream(self.path).pipe(self.dest)
}
return
}
if (self.src && (self.src.method === 'PUT' || self.src.method === 'POST')) {
if (!err) {
// TODO handle overwrite case
return
}
stream.Stream.prototype.pipe.call(self, fs.createWriteStream(self.path))
if (self.dest && self.dest.writeHead) {
self.on('end', function () {
self.dest.statusCode = 201
self.dest.setHeader('content-length', 0)
self.dest.end()
})
}
return
}
// Desination is an HTTP response, we already handled 404 and 500
if (self.dest && self.dest.writeHead) {
self.dest.statusCode = 200
self.dest.setHeader('content-type', self.mimetype)
self.dest.setHeader('etag', self.etag)
self.dest.setHeader('last-modified', self.lastmodified)
self.dest.setHeader('content-length', stats.size)
fs.createReadStream(self.path).pipe(self.dest)
return
}
// Destination is not an HTTP request
if (self.src && !self.dest) {
stream.Stream.prototype.pipe.call(self, fs.createWriteStream(self.path))
} else if (self.dest && !self.src) {
fs.createReadStream(self.path).pipe(self.dest)
}
}
if (!err && stats.isDirectory()) {
self.path = path.join(self.path, self.index)
self.mimetype = mime.getType(self.path.slice(self.path.lastIndexOf('.')+1))
fs.stat(self.path, finish)
return
} else {
finish(err, stats)
}
if (!self.src && !self.dest) {
if (self.buffers.length > 0) {
stream.Stream.prototype.pipe.call(self, fs.createWriteStream(self.path))
} else if (self.listeners('data').length > 0) {
fs.createReadStream(self.path).pipe(self.dest)
} else {
fs.createReadStream(self.path).pipe(self)
}
}
})
}
util.inherits(File, stream.Stream)
File.prototype.pipe = function (dest, options) {
this.dest = dest
this.destOptions = options
dest.emit('pipe', this)
// stream.Stream.prototype.pipe.call(this, dest, options)
return dest
}
File.prototype.write = function (chunk, encoding) {
if (encoding) chunk = chunk.toString(encoding)
if (this.buffering) {
this.buffers.push(chunk)
} else {
this.emit('data', chunk)
}
}
File.prototype.end = function (chunk) {
if (chunk) this.write(chunk)
if (this.buffering) {
this.ended = true
} else {
this.emit('end')
}
}
module.exports = function (options) {
return new File(options)
}
module.exports.File = File