UNPKG

9.81 kBJavaScriptView Raw
1/*jslint node: true, nomen: true, esversion: 6, maxlen: 180 */
2"use strict";
3
4const assert = require('assert');
5const Mime = require('mime');
6const fs = require('fs');
7const Path = require('path');
8const request = require('request');
9const crypto = require('crypto');
10const Async = require('async');
11const Url = require('url');
12const https = require('https');
13
14const debug = require('debug')('upnpserver:contentProviders:1Fichier');
15const logger = require('../logger');
16
17const URL = require('../util/url');
18
19const ContentProvider = require('./contentProvider');
20
21const DIRECTORY_MIME_TYPE = "inode/directory";
22const USER_AGENT = 'Upnpserver/1.0';
23
24var streamID=0;
25
26class OneFichierContentProvider extends ContentProvider {
27
28 constructor(configuration) {
29 super(configuration);
30
31 this._cache={};
32
33 this._userAgent = USER_AGENT;
34
35 this._requestQueue = Async.queue((task, callback) => task(callback), configuration.maxRequest || 2);
36
37 if (!configuration.username) {
38 throw new Error("Username must be specified !");
39 }
40 if (!configuration.password) {
41 throw new Error("Password must be specified !");
42 }
43
44 this._baseURL=configuration.baseURL || "https://1fichier.com/";
45 this._username=this.normalizeParameter(configuration.username);
46 var password=this.normalizeParameter(configuration.password);
47 this._password=password;
48 this._passwordMD5=crypto.createHash('md5').update(password).digest("hex");
49
50 if (!this._username || !this._password) {
51 throw new Error("Username or password must be defined !");
52 }
53
54 debug("Set baseURL to", this._baseURL, "username=", this._username, "password=", this._passwordMD5);
55 }
56
57 initialize(service, callback) {
58 super.initialize(service, (error) => {
59 if (error) {
60 return callback(error);
61 }
62
63 this._userAgent=[ "Node/" + process.versions.node, "UPnP/1.0",
64 "UPnPServer/" + service.upnpServer.packageDescription.version ].join(' ');
65
66 callback();
67 });
68 }
69
70 /**
71 *
72 */
73 _convertURL(contentURL) {
74 var url=contentURL.substring(this.protocol.length+1);
75
76 if (!url || url==='/') {
77 url=this._baseURL+"console/get_folder_content.pl";
78
79 } else {
80 var reg=/([^/]+)\/([^\/]+)$/.exec(url);
81
82 if (reg) {
83 url = this._baseURL+"console/get_folder_content.pl?id="+reg[2];
84 } else {
85 reg=/\?([^?]+)$/.exec(url);
86 url = this._baseURL+"?"+reg[1];
87 }
88 }
89
90 debug("Convert content URL of",contentURL,"=>",url);
91
92 return url;
93 }
94
95 /**
96 *
97 */
98 readdir(contentURL, callback) {
99 var url=this._convertURL(contentURL);
100
101 var folderId="0";
102
103 var reg=/\/([^\/]+)$/.exec(contentURL);
104 if (reg) {
105 folderId=reg[1];
106 }
107
108 debug("Readdir",contentURL,"folderId=",folderId);
109
110 this._requestQueue.push((callback) => this._readdir(url, folderId, callback), callback);
111 }
112
113 _readdir(url, folderId, callback) {
114
115 if (this._badPassword) {
116 return callback(new Error("Bad password detected !"));
117 }
118
119 var options = {
120 qs: {
121 user: this._username,
122 pass: this._passwordMD5
123 }
124 };
125
126 request(url, options, (error, response, body) => {
127 // debug("Readdir Body=",body);
128 if (error) {
129 logger.error("Can not read directory ",url,error);
130 error.url=url;
131 return callback(error);
132 }
133
134 if (response.statusCode===403) {
135 this._badPassword=true;
136 return callback(new Error("Bad password detected !"));
137 }
138
139 var json;
140 try {
141 json = JSON.parse(body);
142 } catch (x) {
143 x.url=url;
144 x.body=body;
145 return callback(x);
146 }
147
148 debug("_readDir", "json=", json);
149
150 if (!(json instanceof Array)) {
151 var err=new Error("Invalid readdir response for url="+folderId);
152 err.body=body;
153
154 return callback(err);
155 }
156
157 var ret=json.map((f) => {
158
159 var stat=this._createStat(f, folderId);
160
161 this._cache[stat.url]=stat;
162
163 debug("_readDir", "stat=", stat);
164
165 return this.newURL(stat.url);
166 });
167
168 callback(null, ret);
169 });
170 }
171
172 _createStat(f, parentId) {
173 var stat={
174 name: f.name,
175 mtime: new Date(f.date),
176 type: f.type ,
177 isDirectory: () => f.type==="d",
178 isFile: () => f.type!=="d"
179 };
180
181 if (f.type==="d") {
182 var reg=/console\/get_folder_content\.pl\?id=(.+)$/.exec(f.url);
183 stat.url=this.protocol+":"+parentId+"/"+(reg && reg[1]);
184 stat.mimeType=DIRECTORY_MIME_TYPE;
185
186 } else {
187 stat.size=parseInt(f.size, 10);
188
189 var reg2=/\?(.+)$/.exec(f.url);
190 stat.url=this.protocol+":"+parentId+"?"+reg2[1];
191 stat.mimeType=f.mimeType || Mime.lookup(f.name);
192 }
193
194 return stat;
195 }
196
197 /**
198 *
199 */
200 stat(contentURL, callback) {
201
202 var stat=this._cache[contentURL];
203 if (stat) {
204 debug("stat", "Stat is in cache",stat);
205 return callback(null, stat);
206 }
207
208 if (true) {
209 // Stat parent !
210
211 var reg=/:([^/]+)\/([^\/]+)$/.exec(contentURL);
212 if (!reg) {
213 reg=/:([^?]*)\?(\/.+)$/.exec(contentURL);
214 }
215
216 var url=this._baseURL+"console/get_folder_content.pl?id="+reg[1];
217
218 this._readdir(url, reg[1], (error, stats) => {
219 if (error) {
220 return callback(error);
221 }
222
223 var stat=stats.find((s) => s.url===contentURL);
224
225 callback(null, stat);
226 });
227
228 return;
229 }
230
231 this._requestQueue.push((callback) => this._stat(contentURL, callback), callback);
232 }
233
234 _stat(contentURL, callback) {
235
236 if (this._badPassword) {
237 return callback(new Error("Bad password detected !"));
238 }
239
240 var url=this._convertURL(contentURL);
241
242 debug("_stat", "contentURL=", contentURL);
243 var options = {
244 method: "HEAD",
245 followRedirect: false,
246 qs: {
247 user: this._username,
248 pass: this._passwordMD5
249 }
250 };
251
252 debug("_stat", "Http request", options);
253
254 request(url, options, (error, response, body) => {
255 debug("_stat", "Body=",body);
256 if (error) {
257 return callback(error);
258 }
259
260 if (response.statusCode===403) {
261 this._badPassword=true;
262 return callback(new Error("Bad password detected !"));
263 }
264
265 var ret=[];
266
267 callback(null, ret);
268 });
269 }
270
271 /**
272 *
273 */
274 createReadStream(session, contentURL, options, callback) {
275 debug("createReadStream", "Create url=", contentURL, "options=",options);
276
277 var url=this._convertURL(contentURL);
278
279 this._requestQueue.push((callback) => this._createReadStream(url, options, callback), (error, stream) => {
280 if (error) {
281 logger.error("_createReadStream from url="+url+" throws an error", error);
282 return callback(error);
283 }
284
285 debug("createReadStream", "returns stream");
286 callback(null, stream);
287 });
288 }
289
290 /**
291 *
292 */
293 _createReadStream(url, options, callback) {
294 debug("_createReadStream", "url=",url,"options=",options);
295
296 if (this._badPassword) {
297 return callback(new Error("Bad password detected !"));
298 }
299
300 var requestOptions = Url.parse(url);
301 requestOptions.headers=requestOptions.headers || {};
302 requestOptions.headers.Authorization="Basic "+new Buffer(this._username + ":" + this._password).toString("base64");
303 requestOptions.headers['User-Agent']=this._userAgent;
304
305 if (options) {
306 if (options.start) {
307 var bs="bytes "+options.start;
308 if (options.end) {
309 bs+=options.end+"/"+(options.end-options.start+1);
310 } else {
311 bs+="*/*";
312 }
313 requestOptions.headers['content-range']=bs;
314 }
315 }
316 debug("Request options=", requestOptions);
317
318 var req = https.request(requestOptions, (response) => {
319
320 var sid=streamID++;
321
322 debug("_createReadStream", "Get response url=", url, "statusCode=",response.statusCode,"stream=#",sid);
323
324 if (response.statusCode===403) {
325 this._badPassword=true;
326 return callback(new Error("Bad password detected !"));
327 }
328
329 if (response.statusCode===302) {
330 var location=response.headers.location;
331 debug("_createReadStream", "Redirect to",location);
332
333 if (location && location!==url) {
334 setImmediate(() => {
335 this._createReadStream(location, options, callback);
336 });
337 }
338 return;
339 }
340
341 if (Math.floor(response.statusCode/100)!=2) {
342 logger.error("Invalid status code "+response.statusCode+" for url "+url);
343 var ex=new Error("Invalid status code "+response.statusCode);
344 return callback(ex);
345 }
346
347 if (debug.enabled) {
348 var count=0;
349
350 response.on('data', (chunk) => {
351 count+=chunk.length;
352 debug('Stream #',sid,'(',url,') Load',count,'bytes');
353 });
354
355 response.on('end', (chunk) => {
356 debug('Stream #',sid,'(',url,') CLOSED');
357 });
358 }
359
360 callback(null, response);
361 });
362
363
364 req.on("error", (error) => {
365 logger.error("_createReadStream: Catch error for url=",url,"error=",error,error.stack);
366 error.url=url;
367 callback(error);
368 });
369 req.end();
370 }
371
372 /**
373 *
374 */
375 toString() {
376 return "[1Fichier ContentProvider name='"+this.name+"' username='"+this._username+"']";
377 }
378}
379
380module.exports = OneFichierContentProvider;