]>
Commit | Line | Data |
---|---|---|
ecb6ca0e CMN |
1 | /* |
2 | * This file is free software; you can redistribute it and/or modify | |
3 | * it under the terms of the GNU General Public License, version 2, | |
4 | * as published by the Free Software Foundation. | |
5 | * | |
6 | * In addition to the permissions in the GNU General Public License, | |
7 | * the authors give you unlimited permission to link the compiled | |
8 | * version of this file into combinations with other programs, | |
9 | * and to distribute those combinations without any restriction | |
10 | * coming from the use of this file. (The General Public License | |
11 | * restrictions do apply in other respects; for example, they cover | |
12 | * modification of the file, and distribution when not linked into | |
13 | * a combined executable.) | |
14 | * | |
15 | * This file is distributed in the hope that it will be useful, but | |
16 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 | * General Public License for more details. | |
19 | * | |
20 | * You should have received a copy of the GNU General Public License | |
21 | * along with this program; see the file COPYING. If not, write to | |
22 | * the Free Software Foundation, 51 Franklin Street, Fifth Floor, | |
23 | * Boston, MA 02110-1301, USA. | |
24 | */ | |
25 | ||
26 | #ifndef _MSC_VER | |
27 | # include <sys/types.h> | |
28 | # include <sys/socket.h> | |
29 | # include <netdb.h> | |
30 | #else | |
31 | # include <winsock2.h> | |
32 | # include <Ws2tcpip.h> | |
33 | # pragma comment(lib, "Ws2_32.lib") | |
34 | #endif | |
35 | ||
36 | #include "git2/net.h" | |
37 | #include "git2/pkt.h" | |
38 | #include "git2/common.h" | |
39 | #include "git2/types.h" | |
40 | #include "git2/errors.h" | |
41 | ||
42 | #include "vector.h" | |
43 | #include "transport.h" | |
44 | #include "common.h" | |
1b4f8140 | 45 | #include "netops.h" |
ecb6ca0e CMN |
46 | |
47 | typedef struct { | |
48 | int socket; | |
49 | git_vector refs; | |
50 | git_remote_head **heads; | |
51 | } git_priv; | |
52 | ||
53 | /* The URL should already have been stripped of the protocol */ | |
54 | static int extract_host_and_port(char **host, char **port, const char *url) | |
55 | { | |
56 | char *colon, *slash, *delim; | |
57 | int error = GIT_SUCCESS; | |
58 | ||
59 | colon = strchr(url, ':'); | |
60 | slash = strchr(url, '/'); | |
61 | ||
62 | if (slash == NULL) | |
63 | return git__throw(GIT_EOBJCORRUPTED, "Malformed URL: missing /"); | |
64 | ||
65 | if (colon == NULL) { | |
66 | *port = git__strdup(GIT_DEFAULT_PORT); | |
67 | } else { | |
68 | *port = git__strndup(colon + 1, slash - colon - 1); | |
69 | } | |
70 | if (*port == NULL) | |
71 | return GIT_ENOMEM;; | |
72 | ||
73 | ||
74 | delim = colon == NULL ? slash : colon; | |
75 | *host = git__strndup(url, delim - url); | |
76 | if (*host == NULL) { | |
77 | free(*port); | |
78 | error = GIT_ENOMEM; | |
79 | } | |
80 | ||
81 | return error; | |
82 | } | |
83 | ||
84 | /* | |
85 | * Parse the URL and connect to a server, storing the socket in | |
86 | * out. For convenience this also takes care of asking for the remote | |
87 | * refs | |
88 | */ | |
89 | static int do_connect(git_priv *priv, const char *url) | |
90 | { | |
91 | int s = -1; | |
92 | char *host, *port, *msg; | |
93 | const char prefix[] = "git://"; | |
94 | int error, ret, msg_len, connected = 0; | |
ecb6ca0e CMN |
95 | |
96 | if (!git__prefixcmp(url, prefix)) | |
97 | url += STRLEN(prefix); | |
98 | ||
99 | error = extract_host_and_port(&host, &port, url); | |
1b4f8140 CMN |
100 | s = gitno_connect(host, port); |
101 | connected = 1; | |
ecb6ca0e | 102 | |
1b4f8140 CMN |
103 | error = git_pkt_gen_proto(&msg, &msg_len, url); |
104 | if (error < GIT_SUCCESS) | |
ecb6ca0e | 105 | goto cleanup; |
ecb6ca0e | 106 | |
1b4f8140 CMN |
107 | /* FIXME: Do this in a loop */ |
108 | ret = send(s, msg, msg_len, 0); | |
109 | free(msg); | |
110 | if (ret < 0) { | |
111 | error = git__throw(GIT_EOSERR, "Failed to send request"); | |
112 | goto cleanup; | |
ecb6ca0e CMN |
113 | } |
114 | ||
115 | priv->socket = s; | |
116 | ||
117 | cleanup: | |
ecb6ca0e CMN |
118 | free(host); |
119 | free(port); | |
120 | ||
121 | if (error < GIT_SUCCESS && s > 0) | |
122 | close(s); | |
123 | if (!connected) | |
124 | error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses"); | |
125 | ||
126 | return error; | |
127 | } | |
128 | ||
129 | /* | |
130 | * Read from the socket and store the references in the vector | |
131 | */ | |
132 | static int store_refs(git_priv *priv) | |
133 | { | |
134 | int s = priv->socket; | |
135 | git_vector *refs = &priv->refs; | |
136 | int error = GIT_SUCCESS; | |
7632e249 | 137 | char buffer[1024]; |
ecb6ca0e CMN |
138 | const char *line_end, *ptr; |
139 | int off = 0, ret; | |
7632e249 | 140 | unsigned int bufflen = 0; |
ecb6ca0e CMN |
141 | git_pkt *pkt; |
142 | ||
7632e249 CMN |
143 | memset(buffer, 0x0, sizeof(buffer)); |
144 | ||
ecb6ca0e | 145 | while (1) { |
7632e249 | 146 | ret = recv(s, buffer + off, sizeof(buffer) - off, 0); |
ecb6ca0e CMN |
147 | if (ret < 0) |
148 | return git__throw(GIT_EOSERR, "Failed to receive data"); | |
7632e249 CMN |
149 | if (ret == 0) /* Orderly shutdown, so exit */ |
150 | return GIT_SUCCESS; | |
ecb6ca0e | 151 | |
7632e249 | 152 | bufflen += ret; |
ecb6ca0e CMN |
153 | ptr = buffer; |
154 | while (1) { | |
7632e249 CMN |
155 | if (bufflen == 0) |
156 | break; | |
157 | error = git_pkt_parse_line(&pkt, ptr, &line_end, bufflen); | |
ecb6ca0e | 158 | /* |
7632e249 CMN |
159 | * If the error is GIT_ESHORTBUFFER, it means the buffer |
160 | * isn't long enough to satisfy the request. Break out and | |
161 | * wait for more input. | |
162 | * On any other error, fail. | |
ecb6ca0e | 163 | */ |
7632e249 CMN |
164 | if (error == GIT_ESHORTBUFFER) { |
165 | line_end = ptr; | |
ecb6ca0e | 166 | break; |
7632e249 CMN |
167 | } |
168 | if (error < GIT_SUCCESS) { | |
169 | return error; | |
170 | } | |
ecb6ca0e CMN |
171 | |
172 | error = git_vector_insert(refs, pkt); | |
173 | if (error < GIT_SUCCESS) | |
174 | return error; | |
175 | ||
7632e249 | 176 | if (pkt->type == GIT_PKT_FLUSH) |
ecb6ca0e CMN |
177 | return GIT_SUCCESS; |
178 | ||
7632e249 | 179 | bufflen -= line_end - ptr; |
ecb6ca0e CMN |
180 | ptr = line_end; |
181 | } | |
182 | ||
183 | /* | |
184 | * Move the rest to the start of the buffer | |
185 | */ | |
7632e249 CMN |
186 | memmove(buffer, line_end, bufflen); |
187 | off = bufflen; | |
188 | memset(buffer + off, 0x0, sizeof(buffer) - off); | |
ecb6ca0e CMN |
189 | } |
190 | ||
191 | return error; | |
192 | } | |
193 | ||
194 | /* | |
195 | * Since this is a network connection, we need to parse and store the | |
196 | * pkt-lines at this stage and keep them there. | |
197 | */ | |
198 | static int git_connect(git_transport *transport, git_net_direction direction) | |
199 | { | |
200 | git_priv *priv; | |
201 | int error = GIT_SUCCESS; | |
202 | ||
203 | if (direction == INTENT_PUSH) | |
204 | return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol"); | |
205 | ||
206 | priv = git__malloc(sizeof(git_priv)); | |
207 | if (priv == NULL) | |
208 | return GIT_ENOMEM; | |
209 | ||
210 | memset(priv, 0x0, sizeof(git_priv)); | |
211 | transport->private = priv; | |
212 | error = git_vector_init(&priv->refs, 16, NULL); | |
213 | if (error < GIT_SUCCESS) | |
214 | goto cleanup; | |
215 | ||
216 | /* Connect and ask for the refs */ | |
217 | error = do_connect(priv, transport->url); | |
218 | if (error < GIT_SUCCESS) | |
219 | return error; | |
220 | ||
221 | error = store_refs(priv); | |
222 | ||
223 | cleanup: | |
224 | if (error < GIT_SUCCESS) { | |
225 | git_vector_free(&priv->refs); | |
226 | free(priv); | |
227 | } | |
228 | ||
229 | return error; | |
230 | } | |
231 | ||
232 | static int git_ls(git_transport *transport, git_headarray *array) | |
233 | { | |
234 | git_priv *priv = transport->private; | |
235 | git_vector *refs = &priv->refs; | |
236 | int len = 0; | |
237 | unsigned int i; | |
238 | ||
239 | array->heads = git__calloc(refs->length, sizeof(git_remote_head *)); | |
240 | if (array->heads == NULL) | |
241 | return GIT_ENOMEM; | |
242 | ||
243 | for (i = 0; i < refs->length; ++i) { | |
244 | git_pkt *p = git_vector_get(refs, i); | |
245 | if (p->type != GIT_PKT_REF) | |
246 | continue; | |
247 | ||
248 | ++len; | |
249 | array->heads[i] = &(((git_pkt_ref *) p)->head); | |
250 | } | |
251 | array->len = len; | |
252 | priv->heads = array->heads; | |
253 | ||
254 | return GIT_SUCCESS; | |
255 | } | |
256 | ||
257 | static int git_close(git_transport *transport) | |
258 | { | |
259 | git_priv *priv = transport->private; | |
260 | int s = priv->socket; | |
261 | int error; | |
262 | ||
7632e249 CMN |
263 | /* FIXME: We probably want to send a flush pkt back */ |
264 | ||
ecb6ca0e CMN |
265 | error = close(s); |
266 | if (error < 0) | |
267 | error = git__throw(GIT_EOSERR, "Failed to close socket"); | |
268 | ||
269 | return error; | |
270 | } | |
271 | ||
272 | static void git_free(git_transport *transport) | |
273 | { | |
274 | git_priv *priv = transport->private; | |
275 | git_vector *refs = &priv->refs; | |
276 | unsigned int i; | |
277 | ||
278 | for (i = 0; i < refs->length; ++i) { | |
279 | git_pkt *p = git_vector_get(refs, i); | |
be9fe679 | 280 | git_pkt_free(p); |
ecb6ca0e CMN |
281 | } |
282 | ||
283 | git_vector_free(refs); | |
284 | free(priv->heads); | |
285 | free(priv); | |
286 | free(transport->url); | |
287 | free(transport); | |
288 | } | |
289 | ||
290 | int git_transport_git(git_transport *transport) | |
291 | { | |
292 | transport->connect = git_connect; | |
293 | transport->ls = git_ls; | |
294 | transport->close = git_close; | |
295 | transport->free = git_free; | |
296 | ||
297 | return GIT_SUCCESS; | |
298 | } |