Skip to content

Commit 94e0578

Browse files
committed
add http upload/download support
1 parent 0cbe513 commit 94e0578

6 files changed

Lines changed: 256 additions & 56 deletions

File tree

OpenFlow/src/LoginProvider.ts

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as crypto from "crypto";
12
import * as url from "url";
23
import * as winston from "winston";
34
import * as express from "express";
@@ -13,7 +14,8 @@ import * as LocalStrategy from "passport-local";
1314
import * as passport from "passport";
1415
import { Config } from "./Config";
1516
import { User } from "./User";
16-
import { Base } from "./base";
17+
import { Base, Rights, WellknownIds } from "./base";
18+
1719
import { TokenUser } from "./TokenUser";
1820
import { Crypt } from "./Crypt";
1921
import { Role } from "./Role";
@@ -22,6 +24,12 @@ import { Audit } from "./Audit";
2224
import * as saml from "saml20";
2325
import { SamlProvider } from "./SamlProvider";
2426
import { Util } from "./Util";
27+
// import { multer } from "multer";
28+
// import { GridFsStorage } from "multer-gridfs-storage";
29+
var multer = require('multer');
30+
var GridFsStorage = require('multer-gridfs-storage');
31+
import { GridFSBucket, ObjectID, Db, Cursor } from "mongodb";
32+
const safeObjectID = (s: string | number | ObjectID) => ObjectID.isValid(s) ? new ObjectID(s) : null;
2533

2634
interface IVerifyFunction { (error: any, profile: any): void; }
2735
export class Provider extends Base {
@@ -212,7 +220,8 @@ export class LoginProvider {
212220
allow_personal_nodered: Config.allow_personal_nodered,
213221
auto_create_personal_nodered_group: Config.auto_create_personal_nodered_group,
214222
namespace: Config.namespace,
215-
nodered_domain_schema: Config.nodered_domain_schema
223+
nodered_domain_schema: Config.nodered_domain_schema,
224+
websocket_package_size: Config.websocket_package_size
216225
}
217226
res.end(JSON.stringify(res2));
218227
});
@@ -249,6 +258,125 @@ export class LoginProvider {
249258
} catch (error) {
250259
}
251260
});
261+
262+
263+
app.get("/download/:id", async (req, res) => {
264+
try {
265+
var user: TokenUser = null;
266+
var jwt: string = null;
267+
var authHeader = req.headers.authorization;
268+
if (authHeader) {
269+
user = Crypt.verityToken(authHeader);
270+
jwt = Crypt.createToken(user, "15m");
271+
}
272+
else if (req.user) {
273+
user = new TokenUser(req.user);
274+
jwt = Crypt.createToken(user, "15m");
275+
}
276+
if (user == null) {
277+
return res.status(404).send({ message: 'Route ' + req.url + ' Not found.' });
278+
}
279+
280+
var id = req.params.id;
281+
var rows = await Config.db.query({ _id: safeObjectID(id) }, null, 1, 0, null, "files", jwt);
282+
if (rows == null || rows.length != 1) { return res.status(404).send({ message: 'id ' + id + ' Not found.' }); }
283+
var file = rows[0] as any;
284+
285+
var bucket = new GridFSBucket(Config.db.db);
286+
let downloadStream = bucket.openDownloadStream(safeObjectID(id));
287+
res.set('Content-Type', file.contentType);
288+
res.set('Content-Disposition', 'attachment; filename="' + file.filename + '"');
289+
res.set('Content-Length', file.length);
290+
downloadStream.on("error", function (err) {
291+
res.end();
292+
});
293+
downloadStream.pipe(res);
294+
} catch (error) {
295+
return res.status(500).send({ message: error });
296+
}
297+
});
298+
try {
299+
300+
var t = new Role();
301+
302+
var storage = GridFsStorage({
303+
db: Config.db,
304+
file: (req, file) => {
305+
return new Promise((resolve, reject) => {
306+
crypto.randomBytes(16, (err, buf) => {
307+
if (err) {
308+
return reject(err);
309+
}
310+
// const filename = buf.toString('hex') + path.extname(file.originalname);
311+
const filename = file.originalname;
312+
const fileInfo = {
313+
filename: filename,
314+
metadata: new Base()
315+
};
316+
var user: TokenUser = null;
317+
var jwt: string = null;
318+
var authHeader = req.headers.authorization;
319+
if (authHeader) {
320+
user = Crypt.verityToken(authHeader);
321+
jwt = Crypt.createToken(user, "15m");
322+
}
323+
else if (req.user) {
324+
user = new TokenUser(req.user);
325+
jwt = Crypt.createToken(user, "15m");
326+
}
327+
328+
fileInfo.metadata.name = file.originalname;
329+
(fileInfo.metadata as any).filename = file.originalname;
330+
(fileInfo.metadata as any).path = "";
331+
fileInfo.metadata._acl = [];
332+
fileInfo.metadata._createdby = user.name;
333+
fileInfo.metadata._createdbyid = user._id;
334+
fileInfo.metadata._created = new Date(new Date().toISOString());
335+
fileInfo.metadata._modifiedby = user.name;
336+
fileInfo.metadata._modifiedbyid = user._id;
337+
fileInfo.metadata._modified = fileInfo.metadata._created;
338+
fileInfo.metadata.addRight(user._id, user.name, [Rights.full_control]);
339+
fileInfo.metadata.addRight(WellknownIds.filestore_admins, "filestore admins", [Rights.full_control]);
340+
fileInfo.metadata.addRight(WellknownIds.filestore_users, "filestore users", [Rights.read]);
341+
342+
resolve(fileInfo);
343+
});
344+
});
345+
},
346+
});
347+
var upload = multer({ //multer settings for single upload
348+
storage: storage
349+
}).any();
350+
351+
app.post("/upload", async (req, res) => {
352+
var user: TokenUser = null;
353+
var jwt: string = null;
354+
var authHeader = req.headers.authorization;
355+
if (authHeader) {
356+
user = Crypt.verityToken(authHeader);
357+
jwt = Crypt.createToken(user, "15m");
358+
}
359+
else if (req.user) {
360+
user = new TokenUser(req.user);
361+
jwt = Crypt.createToken(user, "15m");
362+
}
363+
if (user == null) {
364+
return res.status(404).send({ message: 'Route ' + req.url + ' Not found.' });
365+
}
366+
367+
upload(req, res, function (err) {
368+
if (err) {
369+
res.json({ error_code: 1, err_desc: err });
370+
return;
371+
}
372+
LoginProvider.redirect(res, req.headers.referer);
373+
// res.json({ error_code: 0, err_desc: null });
374+
});
375+
});
376+
} catch (error) {
377+
console.error(error);
378+
}
379+
252380
}
253381
static async RegisterProviders(app: express.Express, baseurl: string) {
254382
if (LoginProvider.login_providers.length === 0) {

OpenFlow/src/WebServer.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,37 @@ export class WebServer {
6464
next();
6565
});
6666
this.app.use("/", express.static(path.join(__dirname, "/public")));
67+
// private async _GetFile(id: string): Promise<string> {
68+
// return new Promise<string>(async (resolve, reject) => {
69+
// try {
70+
// var bucket = new GridFSBucket(Config.db.db);
71+
// let downloadStream = bucket.openDownloadStream(safeObjectID(id));
72+
// var bufs = [];
73+
// downloadStream.on('data', (chunk) => {
74+
// bufs.push(chunk);
75+
// });
76+
// downloadStream.on('error', (error) => {
77+
// reject(error);
78+
// });
79+
// downloadStream.on('end', () => {
80+
81+
// // var contentLength = bufs.reduce(function(sum, buf){
82+
// // return sum + buf.length;
83+
// // }, 0);
84+
// var buffer = Buffer.concat(bufs);
85+
// //writeFileSync('/home/allan/Documents/data.png', result.body);
86+
// //result.body = Buffer.from(result.body).toString('base64');
87+
// var result = buffer.toString('base64');
88+
// resolve(result);
89+
// });
90+
// } catch (err) {
91+
// reject(err);
92+
// }
93+
// });
94+
// }
95+
96+
97+
6798

6899
await LoginProvider.configure(this._logger, this.app, baseurl);
69100
await SamlProvider.configure(this._logger, this.app, baseurl);

OpenFlow/src/public/Controllers.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,34 @@ module openflow {
10691069
return blob;
10701070
}
10711071
async Upload() {
1072+
// const e: any = document.querySelector('input[type="file"]');
1073+
var e: any = document.getElementById('fileupload')
1074+
const fd = new FormData();
1075+
for (var i = 0; i < e.files.length; i++) {
1076+
var file = e.files[i];
1077+
fd.append(e.name, file, file.name);
1078+
};
1079+
const xhr = new XMLHttpRequest();
1080+
// xhr.onreadystatechange = () => {
1081+
// if (xhr.readyState == XMLHttpRequest.DONE) {
1082+
// // console.log(xhr.responseText);
1083+
// }
1084+
// }
1085+
xhr.onload = () => {
1086+
if (xhr.status >= 200 && xhr.status < 300) {
1087+
console.log("upload complete");
1088+
// we done!
1089+
if (!this.$scope.$$phase) { this.$scope.$apply(); }
1090+
this.loadData();
1091+
1092+
}
1093+
};
1094+
console.log("open");
1095+
xhr.open('POST', '/upload', true);
1096+
console.log("send");
1097+
xhr.send(fd);
1098+
}
1099+
async Upload_usingapi() {
10721100
var filename = (this.$scope as any).filename;
10731101
var type = (this.$scope as any).type;
10741102
console.log("filename: " + filename + " type: " + type);

OpenFlow/src/public/Files.html

Lines changed: 61 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -37,57 +37,65 @@ <h1 translate lib="web">files</h1>
3737
}
3838
</style>
3939

40-
<table id="table1" class="table table-striped table-hover table-sm" when-scrolled="ctrl.more()" style="width: 100%;">
41-
<thead class="thead-dark">
42-
<tr>
43-
<th scope="col" ng-click="ctrl.ToggleOrder('metadata.name')"><b translate lib="web">name</b></th>
44-
<th scope="col" ng-click="ctrl.ToggleOrder('metadata.path')"><b translate lib="web">path</b></th>
45-
<th scope="col" ng-click="ctrl.ToggleOrder('contentType')"><b translate lib="web">type</b></th>
46-
<th scope="col" ng-click="ctrl.ToggleOrder('metadata._createdby')"><b translate lib="web">createdby</b></th>
47-
<th scope="col" ng-click="ctrl.ToggleOrder('metadata._created')"><b translate lib="web">created</b></th>
48-
<th></th>
49-
<th></th>
50-
<th></th>
51-
</tr>
52-
</thead>
53-
<tbody>
54-
<tr ng-repeat="model in ctrl.models">
55-
<td>{{model.metadata.name}}</td>
56-
<td>{{model.metadata.path}}</td>
57-
<td>{{model.contentType}}</td>
58-
<td>{{model.metadata._createdby}}</td>
59-
<td>
60-
<timesince ng-model="model.metadata._created" />
61-
</td>
62-
<td class="btn-cell">
63-
<a ng-href="" class="table-btn" ng-click="ctrl.Download(model._id)">
40+
<form action="/upload" method="post" enctype="multipart/form-data">
41+
<table id="table1" class="table table-striped table-hover table-sm" when-scrolled="ctrl.more()" style="width: 100%;">
42+
<thead class="thead-dark">
43+
<tr>
44+
<th scope="col" ng-click="ctrl.ToggleOrder('metadata.name')"><b translate lib="web">name</b></th>
45+
<th scope="col" ng-click="ctrl.ToggleOrder('metadata.path')"><b translate lib="web">path</b></th>
46+
<th scope="col" ng-click="ctrl.ToggleOrder('contentType')"><b translate lib="web">type</b></th>
47+
<th scope="col" ng-click="ctrl.ToggleOrder('metadata._createdby')"><b translate lib="web">createdby</b></th>
48+
<th scope="col" ng-click="ctrl.ToggleOrder('metadata._created')"><b translate lib="web">created</b></th>
49+
<th></th>
50+
<th></th>
51+
<th></th>
52+
</tr>
53+
</thead>
54+
<tbody>
55+
<tr ng-repeat="model in ctrl.models">
56+
<td>{{model.metadata.name}}</td>
57+
<td>{{model.metadata.path}}</td>
58+
<td>{{model.contentType}}</td>
59+
<td>{{model.metadata._createdby}}</td>
60+
<td>
61+
<timesince ng-model="model.metadata._created" />
62+
</td>
63+
<td class="btn-cell">
64+
<!-- <a ng-href="" class="table-btn" ng-click="ctrl.Download(model._id)">
6465
<i class="fas fa-file-download" style="color: #007bff;"></i>
65-
</a>
66-
</td>
67-
<td class="btn-cell">
68-
<a ng-href="#/Entity/{{ctrl.collection}}/{{model._id}}" class="table-btn"><i class="az-edit"></i></a>
69-
</td>
70-
<td class="btn-cell">
71-
<a href ng-click="ctrl.DeleteOne(model)" ng-disabled="ctrl.loading==true" class="table-btn"><i
72-
class="az-trash"></i></a>
73-
</td>
74-
</tr>
75-
</tbody>
76-
<tfoot>
77-
<tr>
78-
<td>Upload</td>
79-
<td colspan="4">
80-
<input type="file" ng-disabled="ctrl.loading==true" class="btn upload-btn" ng-model="ctrl.file"
81-
fileread></button>
82-
</td>
83-
<td colspan="3">
84-
<input type="button" ng-disabled="ctrl.loading==true" ng-click="ctrl.Upload()" value="Upload"></input>
85-
</td>
86-
</tr>
87-
<tr>
88-
<div id="myProgress">
89-
<div id="myBar"></div>
90-
</div>
91-
</tr>
92-
</tfoot>
93-
</table>
66+
</a> -->
67+
<a ng-href="/download/{{model._id}}" class="table-btn">
68+
<i class="fas fa-file-download" style="color: #007bff;"></i>
69+
</a>
70+
</td>
71+
<td class="btn-cell">
72+
<a ng-href="#/Entity/{{ctrl.collection}}/{{model._id}}" class="table-btn"><i class="az-edit"></i></a>
73+
</td>
74+
<td class="btn-cell">
75+
<a href ng-click="ctrl.DeleteOne(model)" ng-disabled="ctrl.loading==true" class="table-btn"><i
76+
class="az-trash"></i></a>
77+
</td>
78+
</tr>
79+
</tbody>
80+
<tfoot>
81+
<tr>
82+
<td>Upload</td>
83+
<td colspan="4">
84+
<!-- <input type="file" ng-disabled="ctrl.loading==true" class="btn upload-btn" ng-model="ctrl.file"
85+
fileread></button> -->
86+
<input type="file" name="fileupload" id="fileupload" multiple>
87+
</td>
88+
<td colspan="3">
89+
<!-- <input type="button" ng-disabled="ctrl.loading==true" ng-click="ctrl.Upload()" value="Upload"></input> -->
90+
<input type="button" ng-disabled="ctrl.loading==true" ng-click="ctrl.Upload()" value="Upload"></input>
91+
<!-- <input type="submit" value="Upload" name="submit"> -->
92+
</td>
93+
</tr>
94+
<tr>
95+
<div id="myProgress">
96+
<div id="myBar"></div>
97+
</div>
98+
</tr>
99+
</tfoot>
100+
</table>
101+
</form>

OpenFlow/src/public/WebSocketClient.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module openflow {
4444
public scanCount: number = 0;
4545
public oneSignalId: string = null;
4646
public location: any;
47+
public websocket_package_size: number = 500;
4748
static $inject = ["$rootScope", "$location", "$window"];
4849
public messageQueue: IHashTable<QueuedMessage> = {};
4950
constructor(public $rootScope: ng.IRootScopeService, public $location, public $window: any) {
@@ -268,6 +269,7 @@ module openflow {
268269
this.allow_user_registration = data.allow_user_registration;
269270
this.namespace = data.namespace;
270271
this.nodered_domain_schema = data.nodered_domain_schema;
272+
this.websocket_package_size = data.websocket_package_size;
271273
this._socketObject = new ReconnectingWebSocket(data.wshost);
272274
this._socketObject.onopen = (this.onopen).bind(this);
273275
this._socketObject.onmessage = (this.onmessage).bind(this);
@@ -376,7 +378,7 @@ module openflow {
376378
});
377379
}
378380
private _Send(message: Message, cb: QueuedMessageCallback, status: QueuedMessageStatusCallback): void {
379-
var messages: string[] = this.chunkString(message.data, 500);
381+
var messages: string[] = this.chunkString(message.data, this.websocket_package_size);
380382
if (messages === null || messages === undefined || messages.length === 0) {
381383
var singlemessage: SocketMessage = SocketMessage.frommessage(message, "", 1, 0);
382384
if (message.replyto === null || message.replyto === undefined) {

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"gulp": "^4.0.0",
4444
"gulp-replace": "^1.0.0",
4545
"gulp-shell": "^0.7.1",
46+
"handlebars": "^4.4.3",
4647
"handlebars-loader": "^1.7.1",
4748
"imports-loader": "^0.8.0",
4849
"jquery": "^3.4.1",
@@ -53,6 +54,8 @@
5354
"mimetype": "0.0.8",
5455
"mongodb": "^3.2.7",
5556
"morgan": "^1.9.1",
57+
"multer": "^1.4.2",
58+
"multer-gridfs-storage": "^3.3.0",
5659
"node-red-contrib-auth-saml": "^1.0.6",
5760
"npm": "^6.10.1",
5861
"passport": "^0.4.0",

0 commit comments

Comments
 (0)