UNPKG

9.36 kBJavaScriptView Raw
1const Url = require('url')
2const path = require("path");
3const { Response } = require('node-fetch')
4const contentTypeLookup = require('mime-types').contentType
5
6class SolidRest {
7
8constructor( handlers ) {
9 this.storageHandlers = {}
10 handlers.forEach( handler => {
11 this.storageHandlers[handler.prefix] = handler
12 })
13}
14
15storage(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
20async 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 /* GET
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 /* HEAD
59 */
60 if (options.method === 'HEAD') {
61 if(!objectExists) return _response(null, resOptions, 404)
62 else return _response(null, resOptions, 200)
63 }
64 /* DELETE
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 /* POST
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) // Now returns 400 instead of 404
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 /* PUT
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) // Note: The headers from makeContainers are also returned here
116
117 return _response(null, resOptions, putStatus)
118 }
119 else {
120 return _response(null, resOptions, 405)
121 }
122
123 /**
124 * @param {RequestInfo} body
125 * @param {RequestInit} options
126 * @param {Number} status - Overrules options.status
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// let prefix = options.rest_prefix==="file" ? "" : options.rest_prefix
151// fn = options.scheme+"//"+prefix+pathname + fn
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 // str = _makeStream(str);
162 return ([200,str])
163 }
164
165 /* treats filename ".acl" and ".meta" as extensions
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 /* DEFAULT HEADER
186 link created using .meta and .acl appended to uri
187 content-type assigned by mime-types.lookup
188 date from nodejs Date
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 // [ 'OPTIONS, HEAD, GET, PATCH, POST, PUT, DELETE' ]
201 headers['x-powered-by'] = headers['x-powered-by'] ||
202 self.storage(options).name
203/*
204 const ext = ( path.basename(pathname).startsWith('.') )
205 ? path.basename(pathname)
206 : path.extname(pathname)
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 // TBD : IS THIS CORRECT? IS THE TYPE OF ACL "resource"?
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 headers.link = headers.link ||
244 options.objectType==="Container"
245 ? `<.meta>; rel="describedBy", <.acl>; rel="acl",`
246 +`<http://www.w3.org/ns/ldp#Container>; rel="type",`
247 +`<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"`
248 : `<${fn}.meta>; rel="describedBy", <${fn}.acl>; rel="acl",`
249 +`<http://www.w3.org/ns/ldp#Resource>; rel="type"`
250*/
251
252 } // end of getHeaders()
253 } // end of fetch()
254} // end of SolidRest()
255
256module.exports = exports = SolidRest
257
258/* END */
259
260
261/*
262required
263 getObjectType
264 getResouce
265 getContainer
266 putResource
267 postResource
268 postContainer
269 deleteResource
270 deleteContainer
271 makeContainers
272optional
273 getHeaders
274 text
275 json
276*/