-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathroutes.js
More file actions
220 lines (204 loc) · 7.59 KB
/
routes.js
File metadata and controls
220 lines (204 loc) · 7.59 KB
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
/*
* shorten-node - A URL Shortening web app
* URL Routes
* There are 5 routes here:
* - index, and developer (static pages)
* - navLink, setLink, and getLink
*
* Andrew Kish, Feb 2012 - Dec 2014 - Sept 2019
*/
var Shorten = require('./shorten'), shorten, log; //Utility functions for shoterner
var sanitize = require('sanitize-caja'); //For XSS prevention
var accepts = require('accepts');
var escapeHtml = require('escape-html');
//We pass the app context to the shorten utility handlers since it needs access too
var Routes = function(app){
this.app = app;
shorten = new Shorten(app);
log = app.get('bunyan');
};
/*
* Landing and developer info pages, two functions as simple as it gets
*
*/
Routes.prototype.index = function (req, res){
res.render('index', {devmode: req.app.settings.env, domain: req.app.set('domain')});
};
Routes.prototype.developers = function (req, res){
res.render('developers', {devmode: req.app.settings.env, domain: req.app.set('domain')});
};
/*
* Navigate to a shortened link
* Accepts a 6-32 alphanumeric get request: (http://srtlnk.com/{thisText})
* Returns 302 redirect to either the original URL or back to homepage with an error
* ALSO: Logs link navigation for stats
*/
Routes.prototype.navLink = function (req, res){
var linkHash = req.params[0];
//Is the linkHash ONLY alphanumeric and between 6-32 characters?
if (shorten.isValidLinkHash(linkHash)){
//Gather and clean the data required for logging this hash
var ipaddress = req.connection.remoteAddress ? "0.0.0.0" : req.connection.remoteAddress;
var referrer = req.header('Referrer') ? "" : req.header('Referrer');
var userAgent = req.headers['user-agent'] ? "" : req.headers['user-agent'];
//Here is the actual object that we will pass to the logger
var userInfo = {
'ip' : sanitize(ipaddress),
'userAgent': sanitize(userAgent),
'referrer' : sanitize(referrer)
};
//Do we have that URL? If so, log it and send them
shorten.linkHashLookUp(linkHash, userInfo, function(linkInfo, dbCursor){
if (linkInfo !== false){
//We know that hash, send them!
//console.log("Linking user to: " + linkInfo.linkDestination);
res.redirect(linkInfo.linkDestination);
}else{
linkHash = sanitize(linkHash);
res.render('index', {errorMessage: 'No such link shortened with hash: \'' + linkHash + '\''});
}
});
}
};
/*
*
* Set a new shortened link
* Accepts a form-POST'd "originalURL" - the URL the user wants shortened.
* Returns a json object with the shortened link information that looks like this:
* {shortenError: false, alreadyShortened: false, originalURL: "http://derp.net", shortenedURL: "http://srtlnk.com/xxxxxx"}
*
*/
Routes.prototype.setLink = function (req, res){
var shortenURL = req.body.originalURL;
var domain = req.app.set('domain');
var shortened = {
shortenError: null, //false for no error, otherwise string with error
alreadyShortened: null, //Boolean for if we already had this URL shortened
originalURL: null, //the url we shortened
shortenedURL: null //the full shortlink, including http://
};
if (!shortenURL){
return res.status(400).json({"error": "Missing shortenedURL"});
}
//This is the URL we're shortening
shortened.originalURL = shortenURL;
//Make sure link mostly looks like a URL
if (shorten.isURL(shortenURL, {"require_protocol": true}) === false){
//Throw an error - not a real or supported URI
log.debug(`Invalid or unsupported URI: ${shortenURL}`);
shortened.shortenError = "Invalid or unsupported URI";
res.status(400).json(shortened);
}
//Check to make sure we haven't already shortened this url
shorten.originalURLLookUp(shortenURL, function(originalURLCheck){
if (originalURLCheck === false){
//Add to db
shorten.addNewShortenLink(shortenURL, function(shortURL){
shortened.shortenedURL = "http://" + domain + "/" + shortURL.linkHash;
shortened.shortenError = false;
shortened.alreadyShortened = false;
res.json(shortened);
});
}else{
//Give them the old hash
shortened.shortenedURL = "http://" + domain + "/" + originalURLCheck.linkHash;
shortened.shortenError = false;
shortened.alreadyShortened = true;
res.json(shortened);
}
});
};
/*
*
* Get info about a shortened link
* Accepts a known short link form-POST'd "shortened_url" - eg ("http://srtlnk.com/yyyxxx")
* Returns a json object with detailed stats and information
*
*/
Routes.prototype.getLink = function (req, res){
var shortenedURL = req.body.shortenedURL;
//Strip domain
shortenedURL = shortenedURL.replace("http://" + req.app.set('domain') + "/", "");
if (shorten.isValidLinkHash(shortenedURL)){
//Get stats
shorten.shortenedURLStats(shortenedURL, function(linkStats){
//console.log("link stats");
//console.log(linkStats);
res.json(linkStats);
});
}else{
//404 - no shortlink found with that hash
var errorResponse ={
'originalURL': null,
'linkHash': null,
'timesUsed': null,
'lastUse': null,
'topReferrals': {},
'topUserAgents':{},
'error': 'Not a URL we know, or not a valid shortened hash'
};
res.status(404).json(errorResponse);
}
};
/*
/* Error handler
**/
Routes.prototype.errorHandler = function(err, req, res, next){
var env = process.env.NODE_ENV;
// respect err.status
if (err.status) {
res.statusCode = err.status;
}
// default status code to 500
if (res.statusCode < 400) {
res.statusCode = 500;
}
// write error to console
if (env !== 'test') {
if (res.statusCode !== 404){
log.error(err.stack || JSON.stringify(err));
}
}
// cannot actually respond
if (res._header) {
return req.socket.destroy();
}
// negotiate
var accept = accepts(req);
var type = accept.types('html', 'json', 'text');
// Security header for content sniffing
res.setHeader('X-Content-Type-Options', 'nosniff');
// html
if (type === 'html') {
if (res.statusCode === 404){
log.info("404 :", req.url, " UA: ", req.headers["user-agent"], "IP: ", req.ip);
return res.render("errors/404.html", {
"http_status": res.statusCode,
env
});
}
var stack = (err.stack || '').split('\n').slice(1).map(function(v){ return '<li>' + escapeHtml(v).replace(/ /g, ' ') + '</li>'; }).join('');
res.render('errors/500.html', {
"http_status": res.statusCode,
error: String(err).replace(/ /g, ' ').replace(/\n/g, '<br>'),
showStack: stack,
env
});
// json
} else if (type === 'json') {
var error = {error: true, message: err.message, stack: err.stack};
for (var prop in err){
if (err[prop]){
error[prop] = err[prop];
}
}
var json = JSON.stringify(error);
res.setHeader('Content-Type', 'application/json');
res.end(json);
// plain text
} else {
res.setHeader('Content-Type', 'text/plain');
res.end(err.stack || String(err));
}
};
module.exports = Routes;