Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 40 additions & 40 deletions lib/needle.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,21 +230,21 @@ Needle.prototype.setup = function(uri, options) {
for (var key in defaults.headers)
config.headers[key] = defaults.headers[key];

config.headers['accept'] = options.accept || defaults.accept;
config.headers['user-agent'] = options.user_agent || defaults.user_agent;
config.headers['Accept'] = options.accept || defaults.accept;
config.headers['User-Agent'] = options.user_agent || defaults.user_agent;

if (options.content_type)
config.headers['content-type'] = options.content_type;
config.headers['Content-Type'] = options.content_type;

// set connection header if opts.connection was passed, or if node < 0.11.4 (close)
if (options.connection || close_by_default)
config.headers['connection'] = options.connection || 'close';
config.headers['Connection'] = options.connection || 'close';

if ((options.compressed || defaults.compressed) && typeof zlib != 'undefined')
config.headers['accept-encoding'] = decompressors['br'] ? 'gzip, deflate, br' : 'gzip, deflate';
config.headers['Accept-Encoding'] = decompressors['br'] ? 'gzip, deflate, br' : 'gzip, deflate';

if (options.cookies)
config.headers['cookie'] = cookies.write(options.cookies);
config.headers['Cookie'] = cookies.write(options.cookies);

//////////////////////////////////////////////////
// basic/digest auth
Expand All @@ -259,7 +259,7 @@ Needle.prototype.setup = function(uri, options) {
if (options.auth && (options.auth == 'auto' || options.auth == 'digest')) {
config.credentials = [options.username, options.password];
} else {
config.headers['authorization'] = auth.basic(options.username, options.password);
config.headers['Authorization'] = auth.basic(options.username, options.password);
}
}

Expand All @@ -281,15 +281,15 @@ Needle.prototype.setup = function(uri, options) {
}

if (options.proxy_user)
config.headers['proxy-authorization'] = auth.basic(options.proxy_user, options.proxy_pass);
config.headers['Proxy-Authorization'] = auth.basic(options.proxy_user, options.proxy_pass);
} else {
delete config.proxy;
}
}

// now that all our headers are set, overwrite them if instructed.
for (var h in options.headers)
config.headers[h.toLowerCase()] = options.headers[h];
config.headers[h] = options.headers[h];
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a key part. We need to ensure that h is in PascalCase format, otherwise a user might pass a header in lowercase format which might not overwrite a previously set one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, makes sense, i will add a formatting util function, probably take one from axios and add it here

But what do we do with tests? Why changing headers broke all the tests? I didnt dive much into this problems so i'm unsure

Copy link
Owner

@tomas tomas Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick look suggests that the changes broke a lookup either in the tests or in the code itself:

image

I'm happy to merge your PR but we need to make sure we don't break anything!


config.uri_modifier = get_option('uri_modifier', null);

Expand All @@ -312,7 +312,7 @@ Needle.prototype.start = function() {
var self = this, body, waiting = false, config = this.setup(uri, options);

// unless options.json was set to false, assume boss also wants JSON if content-type matches.
var json = options.json || (options.json !== false && config.headers['content-type'] == 'application/json');
var json = options.json || (options.json !== false && config.headers['Content-Type'] == 'application/json');

if (data) {

Expand All @@ -323,7 +323,7 @@ Needle.prototype.start = function() {
multipart.build(data, boundary, function(err, parts) {
if (err) throw(err);

config.headers['content-type'] = 'multipart/form-data; boundary=' + boundary;
config.headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary;
next(parts);
});

Expand Down Expand Up @@ -370,19 +370,19 @@ Needle.prototype.start = function() {

function next(body) {
if (body) {
if (body.length) config.headers['content-length'] = body.length;
if (body.length) config.headers['Content-Length'] = body.length;

// if no content-type was passed, determine if json or not.
if (!config.headers['content-type']) {
config.headers['content-type'] = json
if (!config.headers['Content-Type']) {
config.headers['Content-Type'] = json
? 'application/json; charset=utf-8'
: 'application/x-www-form-urlencoded'; // no charset says W3 spec.
}
}

// unless a specific accept header was set, assume json: true wants JSON back.
if (options.json && (!options.accept && !(options.headers || {}).accept))
config.headers['accept'] = 'application/json';
config.headers['Accept'] = 'application/json';

self.send_request(1, method, uri, config, body, out, callback);
}
Expand All @@ -403,14 +403,14 @@ Needle.prototype.get_request_opts = function(method, uri, config) {
opts.method = method;
opts.headers = config.headers;

if (!opts.headers['host']) {
if (!opts.headers['Host']) {
// if using proxy, make sure the host header shows the final destination
var target = proxy ? url.parse(uri) : remote;
opts.headers['host'] = target.hostname;
opts.headers['Host'] = target.hostname;

// and if a non standard port was passed, append it to the port header
if (target.port && [80, 443].indexOf(target.port) === -1) {
opts.headers['host'] += ':' + target.port;
opts.headers['Host'] += ':' + target.port;
}
}

Expand Down Expand Up @@ -519,59 +519,59 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,

// if we got cookies, parse them unless we were instructed not to. make sure to include any
// cookies that might have been set on previous redirects.
if (config.parse_cookies && (headers['set-cookie'] || config.previous_resp_cookies)) {
resp.cookies = extend(config.previous_resp_cookies || {}, cookies.read(headers['set-cookie']));
if (config.parse_cookies && (headers['Set-Cookie'] || config.previous_resp_cookies)) {
resp.cookies = extend(config.previous_resp_cookies || {}, cookies.read(headers['Set-Cookie']));
debug('Got cookies', resp.cookies);
}

// if redirect code is found, determine if we should follow it according to the given options.
if (redirect_codes.indexOf(resp.statusCode) !== -1 && self.should_follow(headers.location, config, uri)) {
if (redirect_codes.indexOf(resp.statusCode) !== -1 && self.should_follow(headers['Location'], config, uri)) {
// clear timer before following redirects to prevent unexpected setTimeout consequence
clearTimeout(timer);

if (count <= config.follow_max) {
out.emit('redirect', headers.location);
out.emit('redirect', headers['Location']);

// unless 'follow_keep_method' is true, rewrite the request to GET before continuing.
if (!config.follow_keep_method) {
method = 'GET';
post_data = null;
delete config.headers['content-length']; // in case the original was a multipart POST request.
delete config.headers['Content-Length']; // in case the original was a multipart POST request.
}

// if follow_set_cookies is true, insert cookies in the next request's headers.
// we set both the original request cookies plus any response cookies we might have received.
if (config.follow_set_cookies && utils.host_and_ports_match(headers.location, uri)) {
var request_cookies = cookies.read(config.headers['cookie']);
if (config.follow_set_cookies && utils.host_and_ports_match(headers['Location'], uri)) {
var request_cookies = cookies.read(config.headers['Cookie']);
config.previous_resp_cookies = resp.cookies;
if (Object.keys(request_cookies).length || Object.keys(resp.cookies || {}).length) {
config.headers['cookie'] = cookies.write(extend(request_cookies, resp.cookies));
config.headers['Cookie'] = cookies.write(extend(request_cookies, resp.cookies));
}
} else if (config.headers['cookie']) {
debug('Clearing original request cookie', config.headers['cookie']);
delete config.headers['cookie'];
} else if (config.headers['Cookie']) {
debug('Clearing original request cookie', config.headers['Cookie']);
delete config.headers['Cookie'];
}

if (config.follow_set_referer)
config.headers['referer'] = encodeURI(uri); // the original, not the destination URL.
config.headers['Referer'] = encodeURI(uri); // the original, not the destination URL.

config.headers['host'] = null; // clear previous Host header to avoid conflicts.
config.headers['Host'] = null; // clear previous Host header to avoid conflicts.

var redirect_url = utils.resolve_url(headers.location, uri);
var redirect_url = utils.resolve_url(headers['Location'], uri);
debug('Redirecting to ' + redirect_url.toString());
return self.send_request(++count, method, redirect_url.toString(), config, post_data, out, callback);
} else if (config.follow_max > 0) {
return done(new Error('Max redirects reached. Possible loop in: ' + headers.location));
return done(new Error('Max redirects reached. Possible loop in: ' + headers['Location']));
}
}

// if auth is requested and credentials were not passed, resend request, provided we have user/pass.
if (resp.statusCode == 401 && headers['www-authenticate'] && config.credentials) {
if (!config.headers['authorization']) { // only if authentication hasn't been sent
var auth_header = auth.header(headers['www-authenticate'], config.credentials, request_opts);
if (resp.statusCode == 401 && headers['WWW-Authenticate'] && config.credentials) {
if (!config.headers['Authorization']) { // only if authentication hasn't been sent
var auth_header = auth.header(headers['WWW-Authenticate'], config.credentials, request_opts);

if (auth_header) {
config.headers['authorization'] = auth_header;
config.headers['Authorization'] = auth_header;
return self.send_request(count, method, uri, config, post_data, out, callback);
}
}
Expand All @@ -582,13 +582,13 @@ Needle.prototype.send_request = function(count, method, uri, config, post_data,
out.emit('headers', headers);

var pipeline = [],
mime = utils.parse_content_type(headers['content-type']),
mime = utils.parse_content_type(headers['Content-Type']),
text_response = mime.type && (mime.type.indexOf('text/') != -1 || !!mime.type.match(/(\/|\+)(xml|json)$/));

// To start, if our body is compressed and we're able to inflate it, do it.
if (headers['content-encoding'] && decompressors[headers['content-encoding']]) {
if (headers['Content-Encoding'] && decompressors[headers['Content-Encoding']]) {

var decompressor = decompressors[headers['content-encoding']]();
var decompressor = decompressors[headers['Content-Encoding']]();

// make sure we catch errors triggered by the decompressor.
decompressor.on('error', had_error);
Expand Down
30 changes: 15 additions & 15 deletions test/basic_auth_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('Basic Auth', function() {
it('doesnt send any Authorization headers', function(done) {
needle.get('localhost:' + port, { parse: true }, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.not.containEql('authorization');
Object.keys(sent_headers).should.not.containEql('Authorization');
done();
})
})
Expand All @@ -41,7 +41,7 @@ describe('Basic Auth', function() {
it('doesnt send any Authorization headers', function(done) {
needle.get('localhost:' + port, { parse: true }, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.not.containEql('authorization');
Object.keys(sent_headers).should.not.containEql('Authorization');
done();
})
})
Expand All @@ -55,15 +55,15 @@ describe('Basic Auth', function() {
it('sends Authorization header', function(done) {
needle.get('localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.containEql('authorization');
Object.keys(sent_headers).should.containEql('Authorization');
done();
})
})

it('Basic Auth only includes username, without colon', function(done) {
needle.get('localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
var auth = get_auth(sent_headers['authorization']);
var auth = get_auth(sent_headers['Authorization']);
auth[0].should.equal('foobar');
auth.should.have.lengthOf(1);
done();
Expand All @@ -79,15 +79,15 @@ describe('Basic Auth', function() {
it('sends Authorization header', function(done) {
needle.get('localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.containEql('authorization');
Object.keys(sent_headers).should.containEql('Authorization');
done();
})
})

it('Basic Auth only includes both username and password', function(done) {
needle.get('localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
var auth = get_auth(sent_headers['authorization']);
var auth = get_auth(sent_headers['Authorization']);
auth[0].should.equal('foobar');
auth[1].should.equal('');
done();
Expand All @@ -103,15 +103,15 @@ describe('Basic Auth', function() {
it('sends Authorization header', function(done) {
needle.get('localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.containEql('authorization');
Object.keys(sent_headers).should.containEql('Authorization');
done();
})
})

it('Basic Auth only includes both username and password', function(done) {
needle.get('localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
var auth = get_auth(sent_headers['authorization']);
var auth = get_auth(sent_headers['Authorization']);
auth[0].should.equal('foobar');
auth[1].should.equal('');
auth.should.have.lengthOf(2);
Expand All @@ -128,15 +128,15 @@ describe('Basic Auth', function() {
it('sends Authorization header', function(done) {
needle.get('localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.containEql('authorization');
Object.keys(sent_headers).should.containEql('Authorization');
done();
})
})

it('Basic Auth only includes both user and password', function(done) {
needle.get('localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
var auth = get_auth(sent_headers['authorization']);
var auth = get_auth(sent_headers['Authorization']);
auth[0].should.equal('foobar');
auth[1].should.equal('jakub');
auth.should.have.lengthOf(2);
Expand All @@ -152,7 +152,7 @@ describe('Basic Auth', function() {

needle.get(url, {}, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.not.containEql('authorization');
Object.keys(sent_headers).should.not.containEql('Authorization');
done();
})
})
Expand All @@ -162,8 +162,8 @@ describe('Basic Auth', function() {

needle.get(url, { username: 'foo' }, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.containEql('authorization');
sent_headers['authorization'].should.eql('Basic Zm9v')
Object.keys(sent_headers).should.containEql('Authorization');
sent_headers['Authorization'].should.eql('Basic Zm9v')
done();
})
})
Expand All @@ -175,15 +175,15 @@ describe('Basic Auth', function() {
it('sends Authorization header', function(done) {
needle.get('foobar:jakub@localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
Object.keys(sent_headers).should.containEql('authorization');
Object.keys(sent_headers).should.containEql('Authorization');
done();
})
})

it('Basic Auth only includes both user and password', function(done) {
needle.get('foobar:jakub@localhost:' + port, opts, function(err, resp) {
var sent_headers = resp.body.headers;
var auth = get_auth(sent_headers['authorization']);
var auth = get_auth(sent_headers['Authorization']);
auth[0].should.equal('foobar');
auth[1].should.equal('jakub');
auth.should.have.lengthOf(2);
Expand Down
4 changes: 2 additions & 2 deletions test/compression_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe('compression', function(){
server = http.createServer(function(req, res) {
var raw = new stream.PassThrough();

var acceptEncoding = req.headers['accept-encoding'];
var acceptEncoding = req.headers['Accept-Encoding'];
if (!acceptEncoding) {
acceptEncoding = '';
}
Expand All @@ -37,7 +37,7 @@ describe('compression', function(){
}

res.setHeader('Content-Type', 'application/json')
if (req.headers['with-bad']) {
if (req.headers['With-Bad']) {
res.end('foo'); // end, no deflate data
} else {
raw.end(jsonData)
Expand Down
Loading
Loading