Skip to content

Commit

Permalink
adding parseHost support
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaly-t committed Jul 16, 2018
1 parent cdd86f9 commit 002d969
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 46 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,23 @@ a.hosts[0].toString(); //=> 'my-host:123'
a.hosts[1].toString(); //=> '[abcd::]:456'
```

### Method `static parseHost(host) => {name,port,isIPv6} | null`

If you use an external list of default hosts, you may need to parse them separately,
before you can use them as correct defaults, which is what this static method does.

```js
const h = ConnectionString.parseHost('[abcd::]:111');
//=> {name: 'abcd', port: 123, isIPv6: true}

const a = new ConnectionString('test://localhost:222/dbname', {hosts: [h]});
//=> {protocol: 'test', hosts: [{name: 'localhost', port: 222, isIPv6: false}], segments: ['dbname']}

a.toString();
//=> test://localhost:222,[abcd::]:111/dbname
```

If no host information found, the method returns `null`.

[WiKi Pages]:https://github.com/vitaly-t/connection-string/wiki
[Optional Format]:https://github.com/vitaly-t/connection-string/wiki#optional-format
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "connection-string",
"version": "0.8.1",
"version": "0.8.2",
"description": "Advanced URL Connection String Parser.",
"main": "src/index.js",
"typings": "src/index.d.ts",
Expand Down
9 changes: 6 additions & 3 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface IHost {

interface IConnectionDefaults {
protocol?: string
hosts?: Array<IHost>;
hosts?: Array<IHost>
user?: string
password?: string
segments?: string[]
Expand All @@ -24,7 +24,10 @@ export class ConnectionString {
segments?: string[];
params?: { [name: string]: string };

toString(): string;
static parseHost(host: string): IHost

toString(): string

setDefaults(defaults: IConnectionDefaults): ConnectionString

setDefaults(defaults: IConnectionDefaults): ConnectionString;
}
104 changes: 63 additions & 41 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@
cs = trim(cs);

// validating URL symbols:
var idx = cs.search(/[^A-Za-z0-9-._~:/?#[\]@!$&'()*+,;=%]/);
if (idx >= 0) {
var s = JSON.stringify(cs[idx]).replace(/^"|"$/g, '\'');
throw new Error('Invalid URL character ' + s + ' at position ' + idx);
}
validateUrl(cs);

// extracting the protocol:
var m = cs.match(/^[\w-_.+!*'()$%]*:\/\//);
Expand Down Expand Up @@ -56,45 +52,18 @@
// if it starts with `/`, it is the first segment, no hosts specified

var endOfHosts = cs.search(/\/|\?/);

var hosts = (endOfHosts === -1 ? cs : cs.substr(0, endOfHosts)).split(',');

for (var i = 0; i < hosts.length; i++) {
var host = hosts[i];
var h = {}, isIPv6 = false;

if (host[0] === '[') {
// This is IPv6, with [::] being the shortest possible
m = host.match(/(\[([0-9a-z:%]{2,45})](?::(-?[0-9]+[^/?]*))?)/i);
isIPv6 = true;
} else {
// It is either IPv4 or a name
m = host.match(/(([a-z0-9%.-]*)(?::(-?[0-9]+[^/?]*))?)/i);
}
if (m) {
if (m[2]) {
h.name = isIPv6 ? m[2] : decode(m[2]);
h.isIPv6 = isIPv6;
}
if (m[3]) {
var p = m[3], port = parseInt(p);
if (port > 0 && port < 65536 && port.toString() === p) {
h.port = port;
} else {
throw new Error('Invalid port: ' + p);
}
}
if (h.name || h.port) {
if (!this.hosts) {
this.hosts = [];
}
Object.defineProperty(h, 'toString', {
value: fullHostName.bind(null, h)
});
this.hosts.push(h);
hosts.forEach(function (str) {
var host = parseHost(str);
if (host) {
if (!this.hosts) {
this.hosts = [];
}
this.hosts.push(host);
}
}
}, this);

if (endOfHosts >= 0) {
cs = cs.substr(endOfHosts);
}
Expand All @@ -109,7 +78,7 @@
}

// extracting parameters:
idx = cs.indexOf('?');
var idx = cs.indexOf('?');
if (idx !== -1) {
cs = cs.substr(idx + 1);
m = cs.match(/([\w-_.+!*'()$%]+)=([\w-_.+!*'()$%]+)/g);
Expand All @@ -128,6 +97,54 @@
}
}

function validateUrl(url) {
var idx = url.search(/[^A-Za-z0-9-._~:/?#[\]@!$&'()*+,;=%]/);
if (idx >= 0) {
var s = JSON.stringify(url[idx]).replace(/^"|"$/g, '\'');
throw new Error('Invalid URL character ' + s + ' at position ' + idx);
}
}

function parseHost(host, external) {
if (external) {
if (typeof host !== 'string') {
throw new TypeError('Invalid \'host\' parameter!');
}
host = trim(host);
}
var m, isIPv6 = false;
if (host[0] === '[') {
// This is IPv6, with [::] being the shortest possible
m = host.match(/(\[([0-9a-z:%]{2,45})](?::(-?[0-9]+[^/?]*))?)/i);
isIPv6 = true;
} else {
// It is either IPv4 or a name
m = host.match(/(([a-z0-9%.-]*)(?::(-?[0-9]+[^/?]*))?)/i);
}
if (m) {
var h = {};
if (m[2]) {
h.name = isIPv6 ? m[2] : decode(m[2]);
h.isIPv6 = isIPv6;
}
if (m[3]) {
var p = m[3], port = parseInt(p);
if (port > 0 && port < 65536 && port.toString() === p) {
h.port = port;
} else {
throw new Error('Invalid port: ' + p);
}
}
if (h.name || h.port) {
Object.defineProperty(h, 'toString', {
value: fullHostName.bind(null, h)
});
return h;
}
}
return null;
}

function toString() {
var s = '';
if (this.protocol) {
Expand Down Expand Up @@ -278,6 +295,11 @@

Object.defineProperty(ConnectionString.prototype, 'toString', {value: toString});
Object.defineProperty(ConnectionString.prototype, 'setDefaults', {value: setDefaults});
Object.defineProperty(ConnectionString, 'parseHost', {
value: function (host) {
return parseHost(host, true);
}
});

ConnectionString.ConnectionString = ConnectionString; // to make it TypeScript-friendly

Expand Down
1 change: 1 addition & 0 deletions test/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ a.setDefaults({
});
cs = a.toString();
var qq = a.setDefaults(new src_1.ConnectionString(''));
var parseHost = src_1.ConnectionString.parseHost('abc');
4 changes: 3 additions & 1 deletion test/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ConnectionString} from '../src'
import {ConnectionString, IHost} from '../src'

const a = new ConnectionString('protocol://');
const b = new ConnectionString('protocol://', {});
Expand Down Expand Up @@ -36,3 +36,5 @@ a.setDefaults({
cs = a.toString();

const qq: ConnectionString = a.setDefaults(new ConnectionString(''));

const parseHost: IHost = ConnectionString.parseHost('abc');
27 changes: 27 additions & 0 deletions test/mainSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,3 +435,30 @@ describe('setDefaults', () => {
});

});

describe('parseHost', () => {
const parseHost = ConnectionString.parseHost;
it('must throw on invalid host', () => {
const error = 'Invalid \'host\' parameter!';
expect(() => {
parseHost();
}).toThrow(error);
expect(() => {
parseHost(123);
}).toThrow(error);
});
it('must allow empty hosts', () => {
expect(parseHost('')).toBeNull();
expect(parseHost(':')).toBeNull();
});
it('must trim hosts', () => {
expect(parseHost(' ')).toBeNull();
expect(parseHost(' : ')).toBeNull();
expect(parseHost('\r\n \t abc\r\n')).toEqual({name: 'abc', isIPv6: false});
});
it('must parse valid hosts', () => {
expect(parseHost('a')).toEqual({name: 'a', isIPv6: false});
expect(parseHost('a:123')).toEqual({name: 'a', port: 123, isIPv6: false});
expect(parseHost('[::]:123')).toEqual({name: '::', port: 123, isIPv6: true});
});
});

0 comments on commit 002d969

Please sign in to comment.