1#include"cache.h" 2#include"repository.h" 3#include"config.h" 4#include"pkt-line.h" 5#include"version.h" 6#include"argv-array.h" 7#include"ls-refs.h" 8#include"serve.h" 9 10static intalways_advertise(struct repository *r, 11struct strbuf *value) 12{ 13return1; 14} 15 16static intagent_advertise(struct repository *r, 17struct strbuf *value) 18{ 19if(value) 20strbuf_addstr(value,git_user_agent_sanitized()); 21return1; 22} 23 24struct protocol_capability { 25/* 26 * The name of the capability. The server uses this name when 27 * advertising this capability, and the client uses this name to 28 * specify this capability. 29 */ 30const char*name; 31 32/* 33 * Function queried to see if a capability should be advertised. 34 * Optionally a value can be specified by adding it to 'value'. 35 * If a value is added to 'value', the server will advertise this 36 * capability as "<name>=<value>" instead of "<name>". 37 */ 38int(*advertise)(struct repository *r,struct strbuf *value); 39 40/* 41 * Function called when a client requests the capability as a command. 42 * The function will be provided the capabilities requested via 'keys' 43 * as well as a struct packet_reader 'request' which the command should 44 * use to read the command specific part of the request. Every command 45 * MUST read until a flush packet is seen before sending a response. 46 * 47 * This field should be NULL for capabilities which are not commands. 48 */ 49int(*command)(struct repository *r, 50struct argv_array *keys, 51struct packet_reader *request); 52}; 53 54static struct protocol_capability capabilities[] = { 55{"agent", agent_advertise, NULL }, 56{"ls-refs", always_advertise, ls_refs }, 57}; 58 59static voidadvertise_capabilities(void) 60{ 61struct strbuf capability = STRBUF_INIT; 62struct strbuf value = STRBUF_INIT; 63int i; 64 65for(i =0; i <ARRAY_SIZE(capabilities); i++) { 66struct protocol_capability *c = &capabilities[i]; 67 68if(c->advertise(the_repository, &value)) { 69strbuf_addstr(&capability, c->name); 70 71if(value.len) { 72strbuf_addch(&capability,'='); 73strbuf_addbuf(&capability, &value); 74} 75 76strbuf_addch(&capability,'\n'); 77packet_write(1, capability.buf, capability.len); 78} 79 80strbuf_reset(&capability); 81strbuf_reset(&value); 82} 83 84packet_flush(1); 85strbuf_release(&capability); 86strbuf_release(&value); 87} 88 89static struct protocol_capability *get_capability(const char*key) 90{ 91int i; 92 93if(!key) 94return NULL; 95 96for(i =0; i <ARRAY_SIZE(capabilities); i++) { 97struct protocol_capability *c = &capabilities[i]; 98const char*out; 99if(skip_prefix(key, c->name, &out) && (!*out || *out =='=')) 100return c; 101} 102 103return NULL; 104} 105 106static intis_valid_capability(const char*key) 107{ 108const struct protocol_capability *c =get_capability(key); 109 110return c && c->advertise(the_repository, NULL); 111} 112 113static intis_command(const char*key,struct protocol_capability **command) 114{ 115const char*out; 116 117if(skip_prefix(key,"command=", &out)) { 118struct protocol_capability *cmd =get_capability(out); 119 120if(*command) 121die("command '%s' requested after already requesting command '%s'", 122 out, (*command)->name); 123if(!cmd || !cmd->advertise(the_repository, NULL) || !cmd->command) 124die("invalid command '%s'", out); 125 126*command = cmd; 127return1; 128} 129 130return0; 131} 132 133inthas_capability(const struct argv_array *keys,const char*capability, 134const char**value) 135{ 136int i; 137for(i =0; i < keys->argc; i++) { 138const char*out; 139if(skip_prefix(keys->argv[i], capability, &out) && 140(!*out || *out =='=')) { 141if(value) { 142if(*out =='=') 143 out++; 144*value = out; 145} 146return1; 147} 148} 149 150return0; 151} 152 153enum request_state { 154 PROCESS_REQUEST_KEYS, 155 PROCESS_REQUEST_DONE, 156}; 157 158static intprocess_request(void) 159{ 160enum request_state state = PROCESS_REQUEST_KEYS; 161struct packet_reader reader; 162struct argv_array keys = ARGV_ARRAY_INIT; 163struct protocol_capability *command = NULL; 164 165packet_reader_init(&reader,0, NULL,0, 166 PACKET_READ_CHOMP_NEWLINE | 167 PACKET_READ_GENTLE_ON_EOF); 168 169/* 170 * Check to see if the client closed their end before sending another 171 * request. If so we can terminate the connection. 172 */ 173if(packet_reader_peek(&reader) == PACKET_READ_EOF) 174return1; 175 reader.options = PACKET_READ_CHOMP_NEWLINE; 176 177while(state != PROCESS_REQUEST_DONE) { 178switch(packet_reader_peek(&reader)) { 179case PACKET_READ_EOF: 180BUG("Should have already died when seeing EOF"); 181case PACKET_READ_NORMAL: 182/* collect request; a sequence of keys and values */ 183if(is_command(reader.line, &command) || 184is_valid_capability(reader.line)) 185argv_array_push(&keys, reader.line); 186else 187die("unknown capability '%s'", reader.line); 188 189/* Consume the peeked line */ 190packet_reader_read(&reader); 191break; 192case PACKET_READ_FLUSH: 193/* 194 * If no command and no keys were given then the client 195 * wanted to terminate the connection. 196 */ 197if(!keys.argc) 198return1; 199 200/* 201 * The flush packet isn't consume here like it is in 202 * the other parts of this switch statement. This is 203 * so that the command can read the flush packet and 204 * see the end of the request in the same way it would 205 * if command specific arguments were provided after a 206 * delim packet. 207 */ 208 state = PROCESS_REQUEST_DONE; 209break; 210case PACKET_READ_DELIM: 211/* Consume the peeked line */ 212packet_reader_read(&reader); 213 214 state = PROCESS_REQUEST_DONE; 215break; 216} 217} 218 219if(!command) 220die("no command requested"); 221 222 command->command(the_repository, &keys, &reader); 223 224argv_array_clear(&keys); 225return0; 226} 227 228/* Main serve loop for protocol version 2 */ 229voidserve(struct serve_options *options) 230{ 231if(options->advertise_capabilities || !options->stateless_rpc) { 232/* serve by default supports v2 */ 233packet_write_fmt(1,"version 2\n"); 234 235advertise_capabilities(); 236/* 237 * If only the list of capabilities was requested exit 238 * immediately after advertising capabilities 239 */ 240if(options->advertise_capabilities) 241return; 242} 243 244/* 245 * If stateless-rpc was requested then exit after 246 * a single request/response exchange 247 */ 248if(options->stateless_rpc) { 249process_request(); 250}else{ 251for(;;) 252if(process_request()) 253break; 254} 255}