Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.

Commit 505223a

Browse files
authored
Merge pull request #2297 from atom/aw/tcp-on-windows
Use TCP sockets on Windows
2 parents 0df9f44 + 9163e51 commit 505223a

10 files changed

+132
-85
lines changed

bin/git-askpass-atom.js

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const net = require('net');
22
const {execFile} = require('child_process');
33

4-
const sockPath = process.argv[2];
4+
const sockAddr = process.argv[2];
55
const prompt = process.argv[3];
66

77
const diagnosticsEnabled = process.env.GIT_TRACE && process.env.GIT_TRACE.length !== 0;
@@ -16,6 +16,28 @@ function log(message) {
1616
process.stderr.write(`git-askpass-atom: ${message}\n`);
1717
}
1818

19+
function getSockOptions() {
20+
const common = {
21+
allowHalfOpen: true,
22+
};
23+
24+
const tcp = /tcp:(\d+)/.exec(sockAddr);
25+
if (tcp) {
26+
const port = parseInt(tcp[1], 10);
27+
if (Number.isNaN(port)) {
28+
throw new Error(`Non-integer TCP port: ${tcp[1]}`);
29+
}
30+
return {port, host: 'localhost', ...common};
31+
}
32+
33+
const unix = /unix:(.+)/.exec(sockAddr);
34+
if (unix) {
35+
return {path: unix[1], ...common};
36+
}
37+
38+
throw new Error(`Malformed $ATOM_GITHUB_SOCK_ADDR: ${sockAddr}`);
39+
}
40+
1941
function userHelper() {
2042
return new Promise((resolve, reject) => {
2143
if (userAskPass.length === 0) {
@@ -43,32 +65,34 @@ function userHelper() {
4365
}
4466

4567
function dialog() {
46-
const payload = {prompt, includeUsername: false, pid: process.pid};
68+
const sockOptions = getSockOptions();
69+
const query = {prompt, includeUsername: false, pid: process.pid};
4770
log('requesting dialog through Atom socket');
4871
log(`prompt = "${prompt}"`);
4972

5073
return new Promise((resolve, reject) => {
51-
const socket = net.connect(sockPath, () => {
74+
const socket = net.connect(sockOptions, () => {
5275
log('connection established');
53-
const parts = [];
76+
let payload = '';
5477

55-
socket.on('data', data => parts.push(data));
78+
socket.on('data', data => {
79+
payload += data;
80+
});
5681
socket.on('end', () => {
5782
log('Atom socket stream terminated');
5883

5984
try {
60-
const replyDocument = JSON.parse(parts.join(''));
85+
const reply = JSON.parse(payload);
6186
log('Atom reply parsed');
62-
resolve(replyDocument.password);
87+
resolve(reply.password);
6388
} catch (err) {
6489
log('Unable to parse reply from Atom');
6590
reject(err);
6691
}
6792
});
6893

69-
log('writing payload');
70-
socket.write(JSON.stringify(payload) + '\u0000', 'utf8');
71-
log('payload written');
94+
log('writing query');
95+
socket.end(JSON.stringify(query), 'utf8', () => log('query written'));
7296
});
7397
socket.setEncoding('utf8');
7498
});

bin/git-askpass-atom.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
#!/bin/sh
2-
ELECTRON_RUN_AS_NODE=1 ELECTRON_NO_ATTACH_CONSOLE=1 "$ATOM_GITHUB_ELECTRON_PATH" "$ATOM_GITHUB_ASKPASS_PATH" "$ATOM_GITHUB_SOCK_PATH" "$@"
2+
ELECTRON_RUN_AS_NODE=1 ELECTRON_NO_ATTACH_CONSOLE=1 "$ATOM_GITHUB_ELECTRON_PATH" "$ATOM_GITHUB_ASKPASS_PATH" "$ATOM_GITHUB_SOCK_ADDR" "$@"

bin/git-credential-atom.js

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const {createStrategy, UNAUTHENTICATED} = require(process.env.ATOM_GITHUB_KEYTAR
1111
const diagnosticsEnabled = process.env.GIT_TRACE && process.env.GIT_TRACE.length !== 0;
1212
const workdirPath = process.env.ATOM_GITHUB_WORKDIR_PATH;
1313
const inSpecMode = process.env.ATOM_GITHUB_SPEC_MODE === 'true';
14-
const sockPath = process.argv[2];
14+
const sockAddr = process.argv[2];
1515
const action = process.argv[3];
1616

1717
const rememberFile = path.join(__dirname, 'remember');
@@ -27,6 +27,28 @@ function log(message) {
2727
process.stderr.write(`git-credential-atom: ${message}\n`);
2828
}
2929

30+
function getSockOptions() {
31+
const common = {
32+
allowHalfOpen: true,
33+
};
34+
35+
const tcp = /tcp:(\d+)/.exec(sockAddr);
36+
if (tcp) {
37+
const port = parseInt(tcp[1], 10);
38+
if (Number.isNaN(port)) {
39+
throw new Error(`Non-integer TCP port: ${tcp[1]}`);
40+
}
41+
return {port, host: 'localhost', ...common};
42+
}
43+
44+
const unix = /unix:(.+)/.exec(sockAddr);
45+
if (unix) {
46+
return {path: unix[1], ...common};
47+
}
48+
49+
throw new Error(`Malformed $ATOM_GITHUB_SOCK_ADDR: ${sockAddr}`);
50+
}
51+
3052
/*
3153
* Because the git within dugite was (possibly) built with a different $PREFIX than the user's native git,
3254
* credential helpers or other config settings from the system configuration may not be discovered. Attempt
@@ -47,7 +69,7 @@ function systemCredentialHelpers() {
4769
log('discover credential helpers from system git configuration');
4870
log(`PATH = ${env.PATH}`);
4971

50-
execFile('git', ['config', '--system', '--get-all', 'credential.helper'], {env}, (error, stdout, stderr) => {
72+
execFile('git', ['config', '--system', '--get-all', 'credential.helper'], {env}, (error, stdout) => {
5173
if (error) {
5274
log(`failed to list credential helpers. this is ok\n${error.stack}`);
5375

@@ -279,30 +301,34 @@ async function fromKeytar(query) {
279301
/*
280302
* Request a dialog in Atom by writing a null-delimited JSON query to the socket we were given.
281303
*/
282-
function dialog(query) {
283-
if (query.username) {
284-
query.auth = query.username;
304+
function dialog(q) {
305+
if (q.username) {
306+
q.auth = q.username;
285307
}
286-
const prompt = 'Please enter your credentials for ' + url.format(query);
287-
const includeUsername = !query.username;
308+
const prompt = 'Please enter your credentials for ' + url.format(q);
309+
const includeUsername = !q.username;
310+
311+
const query = {prompt, includeUsername, includeRemember: true, pid: process.pid};
288312

289-
const payload = {prompt, includeUsername, includeRemember: true, pid: process.pid};
313+
const sockOptions = getSockOptions();
290314

291315
return new Promise((resolve, reject) => {
292316
log('requesting dialog through Atom socket');
293317
log(`prompt = "${prompt}" includeUsername = ${includeUsername}`);
294318

295-
const socket = net.connect(sockPath, async () => {
319+
const socket = net.connect(sockOptions, async () => {
296320
log('connection established');
297321

298-
const parts = [];
322+
let payload = '';
299323

300-
socket.on('data', data => parts.push(data));
324+
socket.on('data', data => {
325+
payload += data;
326+
});
301327
socket.on('end', () => {
302328
log('Atom socket stream terminated');
303329

304330
try {
305-
const reply = JSON.parse(parts.join(''));
331+
const reply = JSON.parse(payload);
306332

307333
const writeReply = function(err) {
308334
if (err) {
@@ -311,7 +337,7 @@ function dialog(query) {
311337

312338
const lines = [];
313339
['protocol', 'host', 'username', 'password'].forEach(k => {
314-
const value = reply[k] !== undefined ? reply[k] : query[k];
340+
const value = reply[k] !== undefined ? reply[k] : q[k];
315341
lines.push(`${k}=${value}\n`);
316342
});
317343

@@ -325,16 +351,16 @@ function dialog(query) {
325351
writeReply();
326352
}
327353
} catch (e) {
328-
log(`Unable to parse reply from Atom:\n${e.stack}`);
354+
log(`Unable to parse reply from Atom:\n${payload}\n${e.stack}`);
329355
reject(e);
330356
}
331357
});
332358

333-
log('writing payload');
359+
log('writing query');
334360
await new Promise(r => {
335-
socket.write(JSON.stringify(payload) + '\u0000', 'utf8', r);
361+
socket.end(JSON.stringify(query), 'utf8', r);
336362
});
337-
log('payload written');
363+
log('query written');
338364
});
339365
socket.setEncoding('utf8');
340366
});
@@ -424,7 +450,7 @@ async function erase() {
424450
}
425451

426452
log(`working directory = ${workdirPath}`);
427-
log(`socket path = ${sockPath}`);
453+
log(`socket address = ${sockAddr}`);
428454
log(`action = ${action}`);
429455

430456
switch (action) {

bin/git-credential-atom.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
#!/bin/sh
2-
ELECTRON_RUN_AS_NODE=1 ELECTRON_NO_ATTACH_CONSOLE=1 "$ATOM_GITHUB_ELECTRON_PATH" "$ATOM_GITHUB_CREDENTIAL_PATH" "$ATOM_GITHUB_SOCK_PATH" "$@"
2+
ELECTRON_RUN_AS_NODE=1 ELECTRON_NO_ATTACH_CONSOLE=1 "$ATOM_GITHUB_ELECTRON_PATH" "$ATOM_GITHUB_CREDENTIAL_PATH" "$ATOM_GITHUB_SOCK_ADDR" "$@"

lib/git-prompt-server.js

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,67 @@
11
import net from 'net';
22
import {Emitter} from 'event-kit';
3+
import {normalizeGitHelperPath} from './helpers';
34

45
export default class GitPromptServer {
56
constructor(gitTempDir) {
67
this.emitter = new Emitter();
78
this.gitTempDir = gitTempDir;
9+
this.address = null;
810
}
911

1012
async start(promptForInput) {
1113
this.promptForInput = promptForInput;
1214

1315
await this.gitTempDir.ensure();
14-
this.server = await this.startListening(this.gitTempDir.getSocketPath());
16+
this.server = await this.startListening(this.gitTempDir.getSocketOptions());
1517
}
1618

17-
startListening(socketPath) {
19+
getAddress() {
20+
/* istanbul ignore if */
21+
if (!this.address) {
22+
throw new Error('Server is not listening');
23+
} else if (this.address.port) {
24+
// TCP socket
25+
return `tcp:${this.address.port}`;
26+
} else {
27+
// Unix domain socket
28+
return `unix:${normalizeGitHelperPath(this.address)}`;
29+
}
30+
}
31+
32+
startListening(socketOptions) {
1833
return new Promise(resolve => {
19-
const server = net.createServer(connection => {
34+
const server = net.createServer({allowHalfOpen: true}, connection => {
2035
connection.setEncoding('utf8');
2136

22-
const parts = [];
23-
37+
let payload = '';
2438
connection.on('data', data => {
25-
const nullIndex = data.indexOf('\u0000');
26-
if (nullIndex === -1) {
27-
parts.push(data);
28-
} else {
29-
parts.push(data.substring(0, nullIndex));
30-
this.handleData(connection, parts.join(''));
31-
}
39+
payload += data;
40+
});
41+
42+
connection.on('end', () => {
43+
this.handleData(connection, payload);
3244
});
3345
});
3446

35-
server.listen(socketPath, () => resolve(server));
47+
server.listen(socketOptions, () => {
48+
this.address = server.address();
49+
resolve(server);
50+
});
3651
});
3752
}
3853

39-
handleData(connection, data) {
54+
async handleData(connection, data) {
4055
let query;
4156
try {
4257
query = JSON.parse(data);
58+
const answer = await this.promptForInput(query);
59+
await new Promise(resolve => {
60+
connection.end(JSON.stringify(answer), 'utf8', resolve);
61+
});
4362
} catch (e) {
44-
this.emitter.emit('did-cancel');
63+
this.emitter.emit('did-cancel', query.pid ? {handlerPid: query.pid} : undefined);
4564
}
46-
47-
Promise.resolve(this.promptForInput(query))
48-
.then(answer => connection.end(JSON.stringify(answer), 'utf-8'))
49-
.catch(() => this.emitter.emit('did-cancel', {handlerPid: query.pid}));
5065
}
5166

5267
onDidCancel(cb) {

lib/git-shell-out-strategy.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ export default class GitShellOutStrategy {
117117

118118
if (execPathPromise === null) {
119119
// Attempt to collect the --exec-path from a native git installation.
120-
execPathPromise = new Promise((resolve, reject) => {
121-
childProcess.exec('git --exec-path', (error, stdout, stderr) => {
120+
execPathPromise = new Promise(resolve => {
121+
childProcess.exec('git --exec-path', (error, stdout) => {
122122
/* istanbul ignore if */
123123
if (error) {
124124
// Oh well
@@ -166,7 +166,7 @@ export default class GitShellOutStrategy {
166166
env.ATOM_GITHUB_ASKPASS_PATH = normalizeGitHelperPath(gitTempDir.getAskPassJs());
167167
env.ATOM_GITHUB_CREDENTIAL_PATH = normalizeGitHelperPath(gitTempDir.getCredentialHelperJs());
168168
env.ATOM_GITHUB_ELECTRON_PATH = normalizeGitHelperPath(getAtomHelperPath());
169-
env.ATOM_GITHUB_SOCK_PATH = normalizeGitHelperPath(gitTempDir.getSocketPath());
169+
env.ATOM_GITHUB_SOCK_ADDR = gitPromptServer.getAddress();
170170

171171
env.ATOM_GITHUB_WORKDIR_PATH = this.workingDir;
172172
env.ATOM_GITHUB_DUGITE_PATH = getDugitePath();

lib/git-temp-dir.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,11 @@ export default class GitTempDir {
5656
return path.join(this.root, filename);
5757
}
5858

59-
getSocketPath() {
59+
getSocketOptions() {
6060
if (process.platform === 'win32') {
61-
if (!this.socketPath) {
62-
this.socketPath = path.join(
63-
'\\\\?\\pipe\\',
64-
'gh-' + require('crypto').randomBytes(8).toString('hex'),
65-
'helper.sock',
66-
);
67-
}
68-
return this.socketPath;
61+
return {port: 0, host: 'localhost'};
6962
} else {
70-
return this.getScriptPath('helper.sock');
63+
return {path: this.getScriptPath('helper.sock')};
7164
}
7265
}
7366

0 commit comments

Comments
 (0)