305 lines
		
	
	
	
		
			11 KiB
			
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			305 lines
		
	
	
	
		
			11 KiB
			
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const { assert } = require('chai');
 | ||
| 
 | ||
| const {
 | ||
|   findLinks,
 | ||
|   isLinkSafeToPreview,
 | ||
|   isLinkSneaky,
 | ||
| } = require('../../js/modules/link_previews');
 | ||
| 
 | ||
| describe('Link previews', () => {
 | ||
|   describe('#isLinkSafeToPreview', () => {
 | ||
|     it('returns false for invalid URLs', () => {
 | ||
|       assert.isFalse(isLinkSafeToPreview(''));
 | ||
|       assert.isFalse(isLinkSafeToPreview('https'));
 | ||
|       assert.isFalse(isLinkSafeToPreview('https://'));
 | ||
|       assert.isFalse(isLinkSafeToPreview('https://bad url'));
 | ||
|       assert.isFalse(isLinkSafeToPreview('example.com'));
 | ||
|     });
 | ||
| 
 | ||
|     it('returns false for non-HTTPS URLs', () => {
 | ||
|       assert.isFalse(isLinkSafeToPreview('http://example.com'));
 | ||
|       assert.isFalse(isLinkSafeToPreview('ftp://example.com'));
 | ||
|       assert.isFalse(isLinkSafeToPreview('file://example'));
 | ||
|     });
 | ||
| 
 | ||
|     it('returns false if the link is "sneaky"', () => {
 | ||
|       // See `isLinkSneaky` tests below for more thorough checking.
 | ||
|       assert.isFalse(isLinkSafeToPreview('https://user:pass@example.com'));
 | ||
|       assert.isFalse(isLinkSafeToPreview('https://aquí.example'));
 | ||
|       assert.isFalse(isLinkSafeToPreview('https://aqu%C3%AD.example'));
 | ||
|     });
 | ||
| 
 | ||
|     it('returns true for "safe" urls', () => {
 | ||
|       assert.isTrue(isLinkSafeToPreview('https://example.com'));
 | ||
|       assert.isTrue(
 | ||
|         isLinkSafeToPreview('https://example.com/foo/bar?query=string#hash')
 | ||
|       );
 | ||
|     });
 | ||
|   });
 | ||
| 
 | ||
|   describe('#findLinks', () => {
 | ||
|     it('returns all links if no caretLocation is provided', () => {
 | ||
|       const text =
 | ||
|         'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android';
 | ||
| 
 | ||
|       const expected = [
 | ||
|         'https://github.com/signalapp/Signal-Desktop',
 | ||
|         'https://github.com/signalapp/Signal-Android',
 | ||
|       ];
 | ||
| 
 | ||
|       const actual = findLinks(text);
 | ||
|       assert.deepEqual(expected, actual);
 | ||
|     });
 | ||
| 
 | ||
|     it('includes all links if cursor is not in a link', () => {
 | ||
|       const text =
 | ||
|         'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android';
 | ||
|       const caretLocation = 10;
 | ||
| 
 | ||
|       const expected = [
 | ||
|         'https://github.com/signalapp/Signal-Desktop',
 | ||
|         'https://github.com/signalapp/Signal-Android',
 | ||
|       ];
 | ||
| 
 | ||
|       const actual = findLinks(text, caretLocation);
 | ||
|       assert.deepEqual(expected, actual);
 | ||
|     });
 | ||
| 
 | ||
|     it('excludes a link not at the end if the caret is inside of it', () => {
 | ||
|       const text =
 | ||
|         'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android';
 | ||
|       const caretLocation = 30;
 | ||
| 
 | ||
|       const expected = ['https://github.com/signalapp/Signal-Android'];
 | ||
| 
 | ||
|       const actual = findLinks(text, caretLocation);
 | ||
|       assert.deepEqual(expected, actual);
 | ||
|     });
 | ||
| 
 | ||
|     it('excludes a link not at the end if the caret is at its end', () => {
 | ||
|       const text =
 | ||
|         'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android';
 | ||
|       const caretLocation = 64;
 | ||
| 
 | ||
|       const expected = ['https://github.com/signalapp/Signal-Android'];
 | ||
| 
 | ||
|       const actual = findLinks(text, caretLocation);
 | ||
|       assert.deepEqual(expected, actual);
 | ||
|     });
 | ||
| 
 | ||
|     it('excludes a link at the end of the caret is inside of it', () => {
 | ||
|       const text =
 | ||
|         'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android';
 | ||
|       const caretLocation = 100;
 | ||
| 
 | ||
|       const expected = ['https://github.com/signalapp/Signal-Desktop'];
 | ||
| 
 | ||
|       const actual = findLinks(text, caretLocation);
 | ||
|       assert.deepEqual(expected, actual);
 | ||
|     });
 | ||
| 
 | ||
|     it('includes link at the end if cursor is at its end', () => {
 | ||
|       const text =
 | ||
|         'Check out this link: https://github.com/signalapp/Signal-Desktop\nAnd this one too: https://github.com/signalapp/Signal-Android';
 | ||
|       const caretLocation = text.length;
 | ||
| 
 | ||
|       const expected = [
 | ||
|         'https://github.com/signalapp/Signal-Desktop',
 | ||
|         'https://github.com/signalapp/Signal-Android',
 | ||
|       ];
 | ||
| 
 | ||
|       const actual = findLinks(text, caretLocation);
 | ||
|       assert.deepEqual(expected, actual);
 | ||
|     });
 | ||
|   });
 | ||
| 
 | ||
|   describe('#isLinkSneaky', () => {
 | ||
|     it('returns true for =', () => {
 | ||
|       const link = 'r.id=s.id';
 | ||
|       assert.strictEqual(isLinkSneaky(link), true);
 | ||
|     });
 | ||
| 
 | ||
|     it('returns true for $', () => {
 | ||
|       const link = 'r.id$s.id';
 | ||
|       assert.strictEqual(isLinkSneaky(link), true);
 | ||
|     });
 | ||
| 
 | ||
|     it('returns true for +', () => {
 | ||
|       const link = 'r.id+s.id';
 | ||
|       assert.strictEqual(isLinkSneaky(link), true);
 | ||
|     });
 | ||
| 
 | ||
|     it('returns true for ^', () => {
 | ||
|       const link = 'r.id^s.id';
 | ||
|       assert.strictEqual(isLinkSneaky(link), true);
 | ||
|     });
 | ||
| 
 | ||
|     it('returns true for URLs with a length of 4097 or higher', () => {
 | ||
|       const href = `https://example.com/${'a'.repeat(4077)}`;
 | ||
|       assert.lengthOf(href, 4097, 'Test href is not the proper length');
 | ||
| 
 | ||
|       assert.isTrue(isLinkSneaky(href));
 | ||
|       assert.isTrue(isLinkSneaky(`${href}?foo=bar`));
 | ||
|     });
 | ||
| 
 | ||
|     describe('auth', () => {
 | ||
|       it('returns true for hrefs with auth (or pretend auth)', () => {
 | ||
|         assert.isTrue(isLinkSneaky('https://user:pass@example.com'));
 | ||
|         assert.isTrue(
 | ||
|           isLinkSneaky('http://whatever.com&login=someuser@77777777')
 | ||
|         );
 | ||
|       });
 | ||
|     });
 | ||
| 
 | ||
|     describe('domain', () => {
 | ||
|       it('returns false for all-latin domain', () => {
 | ||
|         const link = 'https://www.amazon.com';
 | ||
|         const actual = isLinkSneaky(link);
 | ||
|         assert.strictEqual(actual, false);
 | ||
|       });
 | ||
| 
 | ||
|       it('returns false for IPv4 addresses', () => {
 | ||
|         assert.isFalse(isLinkSneaky('https://127.0.0.1/path'));
 | ||
|       });
 | ||
| 
 | ||
|       // It's possible that this should return `false` but we'd need to add special logic
 | ||
|       //   for it.
 | ||
|       it('returns true for IPv6 addresses', () => {
 | ||
|         assert.isTrue(
 | ||
|           isLinkSneaky('https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]/path')
 | ||
|         );
 | ||
|         assert.isTrue(isLinkSneaky('https://[::]/path'));
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true for Latin + Cyrillic domain', () => {
 | ||
|         const link = 'https://www.aмazon.com';
 | ||
|         const actual = isLinkSneaky(link);
 | ||
|         assert.strictEqual(actual, true);
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true for Latin + Greek domain', () => {
 | ||
|         const link = 'https://www.αpple.com';
 | ||
|         const actual = isLinkSneaky(link);
 | ||
|         assert.strictEqual(actual, true);
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true for ASCII and non-ASCII mix', () => {
 | ||
|         const link = 'https://www.аррӏе.com';
 | ||
|         const actual = isLinkSneaky(link);
 | ||
|         assert.strictEqual(actual, true);
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true for Latin + High Greek domain', () => {
 | ||
|         const link = `https://www.apple${String.fromCodePoint(0x101a0)}.com`;
 | ||
|         const actual = isLinkSneaky(link);
 | ||
|         assert.strictEqual(actual, true);
 | ||
|       });
 | ||
| 
 | ||
|       it("returns true if the domain doesn't contain a .", () => {
 | ||
|         assert.isTrue(isLinkSneaky('https://example'));
 | ||
|         assert.isTrue(isLinkSneaky('https://localhost'));
 | ||
|         assert.isTrue(isLinkSneaky('https://localhost:3000'));
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true if the domain has any empty labels', () => {
 | ||
|         assert.isTrue(isLinkSneaky('https://example.'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com.'));
 | ||
|         assert.isTrue(isLinkSneaky('https://.example.com'));
 | ||
|         assert.isTrue(isLinkSneaky('https://..example.com'));
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true if the domain is longer than 2048 UTF-16 code points', () => {
 | ||
|         const domain = `${'a'.repeat(2041)}.example`;
 | ||
|         assert.lengthOf(domain, 2049, 'Test domain is the incorrect length');
 | ||
|         const link = `https://${domain}/foo/bar`;
 | ||
|         assert.isTrue(isLinkSneaky(link));
 | ||
|       });
 | ||
|     });
 | ||
| 
 | ||
|     describe('pathname', () => {
 | ||
|       it('returns false for no pathname', () => {
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com'));
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/'));
 | ||
|       });
 | ||
| 
 | ||
|       it('returns false if the pathname contains valid characters', () => {
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo'));
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo/bar'));
 | ||
|         assert.isFalse(
 | ||
|           isLinkSneaky("https://example.com/:/[]@!$&'()*+,;=abc123-._~%")
 | ||
|         );
 | ||
|         assert.isFalse(
 | ||
|           isLinkSneaky(
 | ||
|             'https://lbry.tv/@ScammerRevolts:b0/DELETING-EVERY-FILE-OFF-A-SCAMMERS-LAPTOP-Destroyed:1'
 | ||
|           )
 | ||
|         );
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true if the pathname contains invalid characters', () => {
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/hello world'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/aquí-está'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/hello\x00world'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/hello\nworld'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/hello😈world'));
 | ||
|       });
 | ||
|     });
 | ||
| 
 | ||
|     describe('query string', () => {
 | ||
|       it('returns false for no query', () => {
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo'));
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo?'));
 | ||
|       });
 | ||
| 
 | ||
|       it('returns false if the query string contains valid characters', () => {
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo?bar'));
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo?bar=baz'));
 | ||
|         assert.isFalse(
 | ||
|           isLinkSneaky(
 | ||
|             "https://example.com/foo?bar=:/[]@!$&'()*+,;=abc123-._~%"
 | ||
|           )
 | ||
|         );
 | ||
|         assert.isFalse(
 | ||
|           isLinkSneaky(
 | ||
|             "https://example.com/foo?:/[]@!$&'()*+,;=abc123-._~%=baz"
 | ||
|           )
 | ||
|         );
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true if the query string contains invalid characters', () => {
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo?bar baz'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo?bar baz=qux'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo?bar=baz qux'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo?aquí=está'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo?hello=\x00world'));
 | ||
|         assert.isTrue(
 | ||
|           isLinkSneaky('https://example.com/foo?hello=hello\nworld')
 | ||
|         );
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo?hello=😈world'));
 | ||
|       });
 | ||
|     });
 | ||
| 
 | ||
|     describe('hash', () => {
 | ||
|       it('returns false for no hash', () => {
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo'));
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo#'));
 | ||
|       });
 | ||
| 
 | ||
|       it('returns false if the hash contains valid characters', () => {
 | ||
|         assert.isFalse(isLinkSneaky('https://example.com/foo#bar'));
 | ||
|         assert.isFalse(
 | ||
|           isLinkSneaky("https://example.com/foo#:/[]@!$&'()*+,;=abc123-._~%")
 | ||
|         );
 | ||
|       });
 | ||
| 
 | ||
|       it('returns true if the hash contains invalid characters', () => {
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo#bar baz'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo#bar baz=qux'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo#bar=baz qux'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo#aquí_está'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo#hello\x00world'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo#hello\nworld'));
 | ||
|         assert.isTrue(isLinkSneaky('https://example.com/foo#hello😈world'));
 | ||
|       });
 | ||
|     });
 | ||
|   });
 | ||
| });
 | 
