1 | const Url = require('url')
|
2 | const path = require("path");
|
3 | const { Response } = require('node-fetch')
|
4 | const contentTypeLookup = require('mime-types').contentType
|
5 |
|
6 | class SolidRest {
|
7 |
|
8 | constructor( handlers ) {
|
9 | this.storageHandlers = {}
|
10 | handlers.forEach( handler => {
|
11 | this.storageHandlers[handler.prefix] = handler
|
12 | })
|
13 | }
|
14 |
|
15 | storage(options){
|
16 | if(!this.storageHandlers[options.rest_prefix]) throw "Did not recognize prefix "+options.rest_prefix
|
17 | return this.storageHandlers[options.rest_prefix]
|
18 | }
|
19 |
|
20 | async fetch(uri, options) {
|
21 | const self = this
|
22 | options = Object.assign({}, options)
|
23 | options.headers = options.headers || {}
|
24 | options.url = decodeURIComponent(uri)
|
25 | let pathname = decodeURIComponent(Url.parse(uri).pathname)
|
26 | options.method = (options.method || options.Method || 'GET').toUpperCase()
|
27 | let scheme = Url.parse(uri).protocol
|
28 | let prefix = scheme.match("file") ? 'file' : uri.replace(scheme+'//','').replace(/\/.*/,'')
|
29 | options.scheme = scheme
|
30 | options.rest_prefix = prefix
|
31 | const [objectType,objectExists] =
|
32 | await self.storage(options).getObjectType(pathname,options)
|
33 | options.objectType = objectType
|
34 | options.objectExists = objectExists
|
35 | const notFoundMessage = '404 Not Found'
|
36 |
|
37 | const resOptions = Object.assign({}, options)
|
38 | resOptions.headers = {}
|
39 |
|
40 | |
41 |
|
42 | if (options.method === 'GET') {
|
43 | if(!objectExists) return _response(notFoundMessage, resOptions, 404)
|
44 | if( objectType==="Container"){
|
45 | let contents = await self.storage(options).getContainer(pathname,options)
|
46 | const [status, turtleContents, headers] = await _container2turtle(pathname,options,contents)
|
47 | Object.assign(resOptions.headers, headers)
|
48 |
|
49 | return _response(turtleContents, resOptions, status)
|
50 | }
|
51 | else if( objectType==="Resource" ){
|
52 | const [status, contents, headers] = await self.storage(options).getResource(pathname,options)
|
53 | Object.assign(resOptions.headers, headers)
|
54 |
|
55 | return _response(contents, resOptions, status)
|
56 | }
|
57 | }
|
58 | |
59 |
|
60 | if (options.method === 'HEAD') {
|
61 | if(!objectExists) return _response(null, resOptions, 404)
|
62 | else return _response(null, resOptions, 200)
|
63 | }
|
64 | |
65 |
|
66 | if( options.method==="DELETE" ){
|
67 | if(!objectExists) return _response(notFoundMessage, resOptions, 404)
|
68 | if( objectType==="Container" ){
|
69 | const [status, , headers] = await self.storage(options).deleteContainer(pathname,options)
|
70 | Object.assign(resOptions.headers, headers)
|
71 |
|
72 | return _response(null, resOptions, status)
|
73 | }
|
74 | else if (objectType === 'Resource' ) {
|
75 | const [status, , headers] = await self.storage(options).deleteResource(pathname,options)
|
76 | Object.assign(resOptions.headers, headers)
|
77 |
|
78 | return _response(null, resOptions, status)
|
79 | }
|
80 | else {
|
81 | }
|
82 | }
|
83 | |
84 |
|
85 | if( options.method==="POST"){
|
86 | if( !objectExists ) return _response(notFoundMessage, resOptions, 404)
|
87 | let link = options.headers.Link || options.headers.link
|
88 | let slug = options.headers.Slug || options.headers.slug
|
89 | if(slug.match(/\//)) return _response(null, resOptions, 400)
|
90 | pathname = path.join(pathname,slug);
|
91 | if( link && link.match("Container") ) {
|
92 | const [status, , headers] = await self.storage(options).postContainer(pathname,options)
|
93 | Object.assign(resOptions.headers, headers)
|
94 |
|
95 | return _response(null, resOptions, status)
|
96 | }
|
97 | else if( link && link.match("Resource")){
|
98 | const [status, , headers] = await self.storage(options).putResource( pathname, options)
|
99 | Object.assign(resOptions.headers, headers)
|
100 |
|
101 | return _response(null, resOptions, status)
|
102 | }
|
103 | }
|
104 | |
105 |
|
106 | if (options.method === 'PUT' ) {
|
107 | if(objectType==="Container") return _response(null, resOptions, 409)
|
108 |
|
109 | const [status, undefined, headers] = await self.storage(options).makeContainers(pathname,options)
|
110 | Object.assign(resOptions.headers, headers)
|
111 |
|
112 | if(status !== 200 && status !== 201) return _response(null, resOptions, status)
|
113 | const [putStatus, , putHeaders] = await self.storage(options).putResource(pathname, options)
|
114 |
|
115 | Object.assign(resOptions.headers, putHeaders)
|
116 |
|
117 | return _response(null, resOptions, putStatus)
|
118 | }
|
119 | else {
|
120 | return _response(null, resOptions, 405)
|
121 | }
|
122 |
|
123 | |
124 |
|
125 |
|
126 |
|
127 |
|
128 | function _response(body, options, status = options.status) {
|
129 | options.status = status
|
130 | options.headers = Object.assign(_getHeaders(pathname, options), options.headers)
|
131 | return new Response(body, options)
|
132 | }
|
133 |
|
134 | async function _container2turtle( pathname, options, contentsArray ){
|
135 | if(typeof self.storage(options).container2turtle != "undefined")
|
136 | return self.storage(options).container2turtle(pathname,options,contentsArray)
|
137 | let filenames=contentsArray.filter( item => {
|
138 | if(!item.endsWith('.acl') && !item.endsWith('.meta')){ return item }
|
139 | })
|
140 | if (!pathname.endsWith("/")) pathname += "/"
|
141 | let str2 = ""
|
142 | let str = "@prefix : <#>. @prefix ldp: <http://www.w3.org/ns/ldp#>.\n"
|
143 | + "<> a ldp:BasicContainer, ldp:Container"
|
144 | if(filenames.length){
|
145 | str = str + "; ldp:contains\n";
|
146 | for(var i=0;i<filenames.length;i++){
|
147 | let fn = filenames[i]
|
148 | let [ftype,e] = await self.storage(options).getObjectType(pathname + fn)
|
149 | if(ftype==="Container" && !fn.endsWith("/")) fn = fn + "/"
|
150 |
|
151 |
|
152 | str = str + ` <${fn}>,\n`
|
153 | let ctype = _getContentType(_getExtension(fn),options.objectType)
|
154 | ftype = ftype==="Container" ? "ldp:Container; a ldp:BasicContainer" : "ldp:Resource"
|
155 | str2 = str2 + `<${fn}> a ${ftype}.\n`
|
156 | str2 = str2 + `<${fn}> :type "${ctype}".\n`
|
157 | }
|
158 | str = str.replace(/,\n$/,"")
|
159 | }
|
160 | str = str + `.\n` + str2
|
161 |
|
162 | return ([200,str])
|
163 | }
|
164 |
|
165 | |
166 |
|
167 | function _getExtension(pathname) {
|
168 | let ext = ( path.basename(pathname).startsWith('.') )
|
169 | ? path.basename(pathname)
|
170 | : path.extname(pathname)
|
171 | return ext
|
172 | }
|
173 | function _getContentType(ext,type) {
|
174 | if( ext==='.ttl'
|
175 | || ext==='.acl'
|
176 | || ext==='.meta'
|
177 | || type==="Container"
|
178 | ) {
|
179 | return 'text/turtle'
|
180 | }
|
181 | else {
|
182 | return contentTypeLookup(ext)
|
183 | }
|
184 | }
|
185 | |
186 |
|
187 |
|
188 |
|
189 |
|
190 | function _getHeaders(pathname,options){
|
191 | let fn = pathname.replace(/.*\//,'');
|
192 | let headers = (typeof self.storage(options).getHeaders != "undefined")
|
193 | ? self.storage(options).getHeaders(pathname,options)
|
194 | : {}
|
195 | headers.location = headers.location || options.url
|
196 | headers.date = headers.date ||
|
197 | new Date(Date.now()).toISOString()
|
198 | headers.allow = headers.allow ||
|
199 | [ 'HEAD, GET, POST, PUT, DELETE' ]
|
200 |
|
201 | headers['x-powered-by'] = headers['x-powered-by'] ||
|
202 | self.storage(options).name
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 | const ext = _getExtension(pathname)
|
210 |
|
211 | headers['content-type']
|
212 | = headers['content-type']
|
213 | || _getContentType(ext,options.objectType)
|
214 | if(!headers['content-type']){
|
215 | delete headers['content-type']
|
216 | }
|
217 | headers.link = headers.link;
|
218 | if( !headers.link ) {
|
219 | if( ext === '.acl' ) {
|
220 |
|
221 | headers.link =
|
222 | `<http://www.w3.org/ns/ldp#Resource>; rel="type"`
|
223 | }
|
224 | else if( ext === '.meta' ) {
|
225 | headers.link =
|
226 | `<${fn}.acl>; rel="acl",`
|
227 | +`<http://www.w3.org/ns/ldp#Resource>; rel="type"`
|
228 | }
|
229 | else if (options.objectType==='Container') {
|
230 | headers.link =
|
231 | `<.meta>; rel="describedBy", <.acl>; rel="acl",`
|
232 | +`<http://www.w3.org/ns/ldp#Container>; rel="type",`
|
233 | +`<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"`
|
234 | }
|
235 | else {
|
236 | headers.link =
|
237 | `<${fn}.meta>; rel="describedBy", <${fn}.acl>; rel="acl",`
|
238 | +`<http://www.w3.org/ns/ldp#Resource>; rel="type"`
|
239 | }
|
240 | }
|
241 | return headers
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 | }
|
253 | }
|
254 | }
|
255 |
|
256 | module.exports = exports = SolidRest
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|