1 ////////////////////////////////////////////////////////////////////////
2 // FILE: httpup.cpp
3 // AUTHOR: Johannes Winkelmann, jw@tks6.net
4 // COPYRIGHT: (c) 2002-2005 by Johannes Winkelmann
5 // ---------------------------------------------------------------------
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
10 ////////////////////////////////////////////////////////////////////////
11
12 #include <iostream>
13
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
17 #include <dirent.h>
18
19 #include "fileutils.h"
20 #include "httpup.h"
21 #include "configparser.h"
22
23 using namespace std;
24
25 const string HttpUp::DEFAULT_REPOFILE = "REPO";
26 const string HttpUp::REPOCURRENTFILEOLD = "REPO.CURRENT";
27 const string HttpUp::REPOCURRENTFILE = ".httpup-repo.current";
28 const string HttpUp::URLINFO = ".httpup-urlinfo";
29
30
31 HttpUp::HttpUp(const HttpupArgparser& argParser,
32 const string& url, const string& target,
33 const string& fragment, const string& repoFile,
34 bool verifyMd5)
35 : m_baseDirectory(target),
36 m_remoteUrl(url),
37 m_fragment(fragment),
38 m_argParser(argParser),
39 m_verifyMd5(verifyMd5)
40 {
41 if (repoFile != "") {
42 m_repoFile = repoFile;
43 } else {
44 m_repoFile = DEFAULT_REPOFILE;
45 }
46 }
47
48
49 int HttpUp::parseCurrent()
50 {
51 FILE* fp = fopen((m_baseDirectory+"/"+REPOCURRENTFILE).c_str(), "r");
52 if (!fp) {
53 // TODO: remove in 0.3.1
54 fp = fopen((m_baseDirectory+"/"+REPOCURRENTFILEOLD).c_str(), "r");
55 if (!fp) {
56 return -1;
57 }
58 }
59 char input[512];
60
61 while (fgets(input, 512, fp)) {
62 input[strlen(input)-1] = '\0';
63 m_actions[string(input)] = REMOVE;
64 }
65
66 return 0;
67 }
68
69 int HttpUp::findDiff()
70 {
71 FILE* fp = fopen((m_baseDirectory + m_repoFile).c_str(), "r");
72 if (!fp) {
73 cerr << "Couldn't open " << m_repoFile << endl;
74 return -1;
75 }
76 char input[512];
77 struct stat info;
78
79 string fileToStat;
80 while (fgets(input, 512, fp)) {
81 input[strlen(input)-1] = '\0';
82 if (input[0] == 'd') {
83
84 string dir = input+2;
85
86 if (m_fragment != "" &&
87 dir.substr(0, m_fragment.length()) != m_fragment) {
88 // doesn't start with fragment
89 continue;
90 }
91
92 if (m_fragment == dir) {
93 continue;
94 }
95
96 if (m_fragment != "") {
97 if (dir.substr(0, m_fragment.length()) == m_fragment &&
98 dir.length() > m_fragment.length()+1 &&
99 dir[m_fragment.length()] == '/') {
100 // strip; matching but hierarchy
101
102 dir = dir.substr(m_fragment.length()+1);
103 if (dir.length() == 0) {
104 continue;
105 }
106 } else {
107 // strip: fragment is only a substring of dir
108 continue;
109 }
110 }
111
112 m_remoteFiles.push_back(dir);
113 fileToStat = m_baseDirectory + (dir);
114 if (stat(fileToStat.c_str(), &info) == 0) {
115 // dir exists
116 if (!S_ISDIR(info.st_mode)) {
117 m_actions[dir] = REPLACE_FILE_WITH_DIR;
118 } else {
119 m_actions[dir] = NOP;
120 }
121 } else {
122 m_actions[dir] = DIR_CREATE;
123 }
124 } else {
125 int fileNameOffset = 2 + 32 + 1;
126 // 0+2+32+1 means
127 // +2 skip the "f:" string
128 // +32 skip the md5 string
129 // +1 skip the separator (':') between fileName and md5
130
131 string file = input+fileNameOffset;
132 if (m_fragment != "" &&
133 file.substr(0, m_fragment.length()) != m_fragment) {
134 // doesn't start with fragment
135 continue;
136 }
137
138 if (m_fragment != "") {
139
140 if (file.substr(0, m_fragment.length()) == m_fragment &&
141 file.length() > m_fragment.length()+1 &&
142 file[m_fragment.length()] == '/') {
143
144 file = file.substr(m_fragment.length()+1);
145 } else {
146 // skip; fragment is only a substring
147 continue;
148 }
149 }
150
151
152 m_remoteFiles.push_back(file);
153 fileToStat = m_baseDirectory + (file);
154 if (stat(fileToStat.c_str(), &info) == 0) {
155
156 if (S_ISDIR(info.st_mode)) {
157 m_actions[file] = REPLACE_DIR_WITH_FILE;
158 } else {
159 // file exists
160 unsigned char result[16];
161 bool diff = false;
162 if (FileUtils::fmd5sum(fileToStat, result)) {
163 input[2+32] = '\0';
164 diff = verifyMd5sum(input+2, result);
165 }
166 if (diff) {
167 m_actions[file] = FILE_GET;
168 } else {
169 m_actions[file] = NOP;
170 }
171 }
172 } else {
173 m_actions[file] = NEW_FILE_GET;
174 }
175
176 if (m_verifyMd5) {
177 m_md5sums[file] = string(input+2);
178 }
179 }
180 }
181 fclose(fp);
182
183 return 0;
184 }
185
186 bool HttpUp::verifyMd5sum(const char* input, unsigned char result[16])
187 {
188 static char hexNumbers[] = {'0','1','2','3','4','5','6','7',
189 '8','9','a','b','c','d','e','f'};
190 bool diff = false;
191
192 unsigned char high, low;
193 for (int i = 0; i < 16; ++i) {
194 high = (result[i] & 0xF0) >> 4;
195 low = result[i] & 0xF;
196 if (*(input+2*i) - hexNumbers[high] ||
197 *(input+2*i+1) - hexNumbers[low]) {
198 diff = true;
199 break;
200 }
201 }
202
203 return diff;
204 }
205
206 int HttpUp::exec(ExecType type)
207 {
208 struct stat info;
209 if (stat(m_baseDirectory.c_str(), &info)) {
210 if (FileUtils::mktree(m_baseDirectory.c_str())) {
211 cerr << "Failed to create base directory "
212 << m_baseDirectory << endl;
213 return -1;
214 }
215 }
216
217 Config config;
218 ConfigParser::parseConfig("/etc/httpup.conf", config);
219
220
221 // TODO: check return values.
222 CURL *curl;
223 curl_global_init(CURL_GLOBAL_ALL);
224 curl = curl_easy_init();
225
226 char errorBuffer[CURL_ERROR_SIZE];
227 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorBuffer);
228 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
229 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
230 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30);
231
232
233 // proxy, proxy auth
234 if (config.proxyHost != "") {
235 curl_easy_setopt(curl, CURLOPT_PROXY, config.proxyHost.c_str());
236 }
237
238 if (config.proxyPort != "") {
239 long port = atol(config.proxyPort.c_str());
240 curl_easy_setopt(curl, CURLOPT_PROXYPORT, port);
241 }
242
243 string usrpwd;
244 if (config.proxyUser != "" || config.proxyPassword != "") {
245 usrpwd = config.proxyUser + ":" + config.proxyPassword;
246 curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, usrpwd.c_str());
247 }
248
249
250 #if 0
251 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
252 #endif
253
254 if (!curl) {
255 cerr << "Failed to initialize CURL engine" << endl;
256 return -1;
257 }
258
259 cout << "Connecting to " << m_remoteUrl << endl;
260 int ret = syncOrReturn(curl, errorBuffer);
261
262 curl_easy_cleanup(curl);
263
264 if (ret == 0) {
265
266 if (type == TYPE_SYNC) {
267 saveRepoCurrent();
268 } else if (type == TYPE_COPY){
269 unlink((m_baseDirectory+m_repoFile).c_str());
270 }
271
272
273 if (type == TYPE_SYNC) {
274 FILE* fp = fopen((m_baseDirectory+"/"+URLINFO).c_str(), "w");
275 if (fp) {
276 fprintf(fp, "%s#%s",
277 m_remoteUrl.c_str(), m_fragment.c_str());
278 fclose(fp);
279 } else {
280 cerr << "Failed to store urlinfo" << endl;
281 }
282 }
283 }
284
285 return ret;
286 }
287
288 int HttpUp::syncOrReturn(CURL* curl, char* curlErrorBuffer)
289 {
290
291 if (getRemoteRepoFile(curl, curlErrorBuffer) != 0) {
292 cerr << "Failed to get remote repo file" << endl;
293 return -1;
294 }
295
296 string collectionName =
297 basename((m_baseDirectory.substr(0, m_baseDirectory.length()-1)).
298 c_str());
299
300 cout << "Updating collection " << collectionName << endl;
301
302 // compare with local directory
303 if (parseCurrent() != 0) {
304 // -- also "fails" the first time...
305 // cerr << "Failed to parse local directory" << endl;
306 // return -1;
307 }
308
309 if (findDiff() != 0) {
310 cerr << "Failed to check for differences" << endl;
311 return -1;
312 }
313
314 #if 0
315 if (m_actions.size() == 0) {
316 cerr << "No matches found for fragment " << m_fragment << endl;
317 return -1;
318 }
319 #endif
320
321
322 return getChangedFiles(collectionName, curl, curlErrorBuffer);;
323 }
324
325 void HttpUp::saveRepoCurrent()
326 {
327 // save current
328 FILE* current = fopen((m_baseDirectory + REPOCURRENTFILE).c_str(), "w");
329 if (!current) {
330 cerr << "Couldn't open "
331 << m_baseDirectory << REPOCURRENTFILE << " for writing" << endl;
332 } else {
333 list<string>::iterator cit = m_remoteFiles.begin();
334 for (; cit != m_remoteFiles.end(); ++cit) {
335 fprintf(current, "%s\n", cit->c_str());
336 }
337 fclose(current);
338 }
339
340 // TODO: remove in 0.3.1
341 FILE* fp = fopen((m_baseDirectory+REPOCURRENTFILEOLD).c_str(), "r");
342 if (fp) {
343 fclose(fp);
344 unlink((m_baseDirectory+REPOCURRENTFILEOLD).c_str());
345 }
346
347 unlink((m_baseDirectory+m_repoFile).c_str());
348 cout << "Finished successfully" << endl;
349 }
350
351 int HttpUp::getChangedFiles(const string& collectionName, CURL* curl,
352 char* curlErrorBuffer)
353 {
354 int errors = 0;
355
356 string fragment = m_fragment;
357 if (fragment != "") {
358 fragment += "/";
359 }
360
361 // synchronize
362 map<string, Action>::iterator it = m_actions.begin();
363 for (; it != m_actions.end(); ++it) {
364
365 if (it->first.substr(0, 3) == "../" ||
366 it->first.find("/../") != string::npos) {
367 cerr << " WARNING: Malicious path in remote REPO file: "
368 << it->first << endl;
369 continue;
370 }
371
372 if (it->second == DIR_CREATE) {
373 cout << " Checkout: "
374 << collectionName << "/" << it->first << endl;
375
376 mkdir((m_baseDirectory+it->first).c_str(), 0755);
377 } else if (it->second == NEW_FILE_GET || it->second == FILE_GET) {
378 if (it->second == NEW_FILE_GET) {
379 cout << " Checkout: "
380 << collectionName << "/" << it->first << endl;
381 } else if (it->second == FILE_GET) {
382 cout << " Edit: "
383 << collectionName << "/" << it->first << endl;
384 }
385
386 string fileName = it->first;
387 if (m_argParser.isSet(HttpupArgparser::OPT_ENCODE)) {
388 char* p = curl_escape(fileName.c_str(), fileName.length());
389 fileName = p;
390 curl_free(p);
391 }
392
393 string fileURL = m_remoteUrl+fragment+fileName;
394 curl_easy_setopt(curl, CURLOPT_URL, fileURL.c_str());
395
396 FILE* dlFile = fopen((m_baseDirectory+it->first).c_str(), "w");
397 if (!dlFile) {
398 cout << " Failed to open " << it->first
399 << " for writing" <<endl;
400 } else {
401 curl_easy_setopt(curl, CURLOPT_FILE, dlFile);
402 CURLcode res = curl_easy_perform(curl);
403 if (res) {
404 cout << "Failed to download " << fileURL
405 << ": " << curlErrorBuffer << endl;
406 }
407 fclose(dlFile);
408 }
409
410 if (m_verifyMd5) {
411 unsigned char result[16];
412 if (FileUtils::fmd5sum(m_baseDirectory+it->first, result)) {
413 bool diff =
414 verifyMd5sum(m_md5sums[it->first.c_str()].c_str(),
415 result);
416
417 if (diff) {
418 cerr << "Bad md5sum after download for "
419 << it->first << endl;
420 ++errors;
421 }
422 } else {
423 ++errors;
424 }
425 }
426
427 } else if (it->second == REPLACE_DIR_WITH_FILE) {
428 cout << " Cowardly refusing to overwrite directory '"
429 << m_baseDirectory+it->first
430 << "' with a file" << endl;
431 continue;
432 } else if (it->second == REPLACE_FILE_WITH_DIR) {
433 cout << " Remove: "
434 << collectionName << "/" << it->first
435 << " (file)"
436 << endl;
437 int ret = FileUtils::deltree((m_baseDirectory+it->first).c_str());
438
439 if (ret == 0) {
440 cout << " Checkout: "
441 << collectionName << "/" << it->first << endl;
442 mkdir((m_baseDirectory+it->first).c_str(), 0755);
443 }
444 } else if (it->second == REMOVE) {
445 cout << " Delete: "
446 << collectionName << "/" << it->first << endl;
447
448 if (FileUtils::deltree((m_baseDirectory+it->first).c_str())) {
449 cout << " Failed to remove " << it->first << endl;
450 m_remoteFiles.push_back(it->first);
451 }
452 }
453 }
454
455 return errors;
456 }
457
458 int HttpUp::getRemoteRepoFile(CURL* curl, char* curlErrorBuffer)
459 {
460 // download repo
461 FILE* dlFile = 0;
462 string fileName = m_repoFile;
463 if (m_argParser.isSet(HttpupArgparser::OPT_ENCODE)) {
464 char* p = curl_escape(fileName.c_str(), fileName.length());
465 fileName = p;
466 curl_free(p);
467 }
468 string repoURL = m_remoteUrl + fileName;
469
470 curl_easy_setopt(curl, CURLOPT_URL, repoURL.c_str());
471 dlFile = fopen((m_baseDirectory+m_repoFile).c_str(), "w");
472 if (!dlFile) {
473 cout << " Failed to open " << m_repoFile << " for writing" << endl;
474 } else {
475
476 curl_easy_setopt(curl, CURLOPT_FILE, dlFile);
477 CURLcode res = curl_easy_perform(curl);
478 if (res) {
479 cerr << " Failed to download " << m_repoFile
480 << ": " << curlErrorBuffer << endl;
481 return -1;
482 }
483 fclose(dlFile);
484 }
485
486
487 return 0;
488 }
|