summaryrefslogtreecommitdiff
path: root/httpup.cpp
blob: 75344048c5e052de1df18f048b4362835d35d1fc (plain)
    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 }

Generated by cgit