📄 sftpc.cpp
字号:
/* * The idea here is I have a bunch of text files with * various line ending conditions. I want to force sftpc * to do every kind of text transformation it knows how to * do, and verify I get what I should. * * The purpose here is *not* to test the *server*'s ability * to do text transformations, so the binaryAnyway flag is * always set whenever we tell sftpc to do an ascii transfer. * * These tests will print warnings about malformed text; * I neither check nor suppress this output. */ // sizeof returns the length *including* the automatically- // appended null terminator, which I don't want included #define BLK(arr) DataBlock b_##arr(arr, sizeof(arr)-1) // well-structured CRLF text char const crlf1[] = "This is a text file.\r\n" "Its lines are separated by CRLF.\r\n" "This is the third and final line.\r\n"; BLK(crlf1); // badly-structured CRLF text char const crlf2[] = "This is another file with CRLF separators.\r\n" "Here (\r) is an embedded CR.\r\n" "Here (\n) is an embedded LF.\r\n" "And finally, a line missing any separator."; BLK(crlf2); // crlf1 -> CRLF_to_LF -> lf1 char const lf1[] = "This is a text file.\n" "Its lines are separated by CRLF.\n" "This is the third and final line.\n"; BLK(lf1); // crlf2 -> CRLF_to_LF -> lf2 char const lf2[] = "This is another file with CRLF separators.\n" "Here () is an embedded CR.\n" "Here () is an embedded LF.\n" "And finally, a line missing any separator."; BLK(lf2); // lf1 -> LF_to_CRLF -> crlf1 (correct inverse) // lf2 -> LF_to_CRLF -> crlf2b char const crlf2b[] = "This is another file with CRLF separators.\r\n" "Here () is an embedded CR.\r\n" "Here () is an embedded LF.\r\n" "And finally, a line missing any separator."; BLK(crlf2b); // crlf2 -> LF_to_CRLF -> crlf2c char const crlf2c[] = "This is another file with CRLF separators.\r\n" "Here () is an embedded CR.\r\n" "Here (\r\n) is an embedded LF.\r\n" "And finally, a line missing any separator."; BLK(crlf2c); // crlf2 -> CRLF_to_CRLF -> crlf2d char const crlf2d[] = "This is another file with CRLF separators.\r\n" "Here () is an embedded CR.\r\n" "Here () is an embedded LF.\r\n" "And finally, a line missing any separator."; BLK(crlf2d); #undef BLK // here are the set of inputs, transforms, and outputs // I believe to be correct enum TransType { CRLF2LF, LF2CRLF, CRLF2CRLF }; struct { DataBlock const *start; // can't use & because of something about ctors TransType tt; DataBlock const *end; } arr[] = { { &b_crlf1, CRLF2LF, &b_lf1 }, { &b_crlf2, CRLF2LF, &b_lf2 }, { &b_lf1, LF2CRLF, &b_crlf1 }, { &b_lf2, LF2CRLF, &b_crlf2b }, { &b_crlf2, LF2CRLF, &b_crlf2c }, { &b_crlf2, CRLF2CRLF, &b_crlf2d } }; // this loop runs each line in the above array loopi(TABLESIZE(arr)) { switch (arr[i].tt) { case LF2CRLF: testSendReceiveBlocks( // ascii local binaryAnyway *arr[i].start, TransferState(true, false, true), *arr[i].end, TransferState(false, false, false)); break; case CRLF2LF: testSendReceiveBlocks( // ascii local binaryAnyway *arr[i].start, TransferState(false, false, false), *arr[i].end, TransferState(true, false, true)); break; case CRLF2CRLF: testSendReceiveBlocks( // ascii local binaryAnyway *arr[i].start, TransferState(true, true, true), *arr[i].end, TransferState(false, false, false)); break; } }}// send and receive some data, and compare what we get// to what we expectvoid SFTPC::testSendReceiveBlocks( DataBlock const &start, TransferState const &sendState, DataBlock const &end, TransferState const &receiveState){ // I had been using the same name for the local and remote file // names, but that created problems when the local and remote // directories were the same // write the initial data to a temporary file start.writeToFile("tempfile.local"); // transfer this file to the server setTransferState(sendState); userCommand("put tempfile.local tempfile.remote"); // retrieve the file from the server setTransferState(receiveState); userCommand("get tempfile.remote tempfile.local"); // read what we got into a buffer DataBlock got; got.readFromFile("tempfile.local"); // compare what we got to what we expect if (end != got) { if (start.getDataLen() + end.getDataLen() + got.getDataLen() < 3000) { // print the failing data if it's not too big start.print("initial"); end.print("expected"); got.print("actually got"); } else { // we already have 'got' as tempfile.local ; write the // others to disk, for later human inspection start.writeToFile("tempfile.start"); end.writeToFile("tempfile.end"); } xfailure("put/get failed to preserve data"); } else { printf(" (files are identical) \n"); // if it worked, go ahead and delete the temp files userCommand("rm tempfile.remote"); // from server removeFile("tempfile.local"); // from cwd }}void SFTPC::setTransferState(TransferState const &state){ #define C(v) v = state.v C(asciiTransfers); C(localConventionIsCRLF); C(binaryTransferAnyway); #undef C}// a series of automated tests to verify that various functions// in sftpc and sftpd are working correctlyvoid SFTPC::runTests(bool passive){ transferPassively = passive; // test a simple ls -l command userCommand("dir"); // test failed send bool eatIt = true; try { userCommand("put crazy.nonexist.file"); eatIt = false; xfailure("failed to fail to put crazy.nonexist.file"); } catch (...) { if (!eatIt) { throw; } } // test real data transfers testBinaryTransfers(); // interpose a failed receive eatIt = true; try { userCommand("get crazier.nonexist.file"); eatIt = false; xfailure("failed to fail to get crazy.nonexist.file"); } catch (...) { if (!eatIt) { throw; } } // bunch of text transfers testTextTransfers(); // test multiple-file commands testMultipleFileCmds();}static void verifyFile(DataBlock const &blk, char const *fname){ DataBlock temp; temp.readFromFile(fname); xassert(blk == temp);}// primary intent here is to test the code that selects// file names and initiates transfers, rather than that// each file's contents are transferred correctlyvoid SFTPC::testMultipleFileCmds(){ // some data to toss around DataBlock a("block A"); DataBlock b("block B"); DataBlock c("block C"); // write them to files a.writeToFile("testmf.a"); b.writeToFile("testmf.b"); c.writeToFile("testmf.c"); // send them interactivePrompting = false; asciiTransfers = false; userCommand("mput testmf.*"); // just to verify it doesn't crash... userCommand("mls testmf.*"); // delete the local versions removeFile("testmf.a"); removeFile("testmf.b"); removeFile("testmf.c"); // get them back from the server userCommand("mget testmf.*"); // verify they all arrived correctly verifyFile(a, "testmf.a"); verifyFile(b, "testmf.b"); verifyFile(c, "testmf.c"); // remove them from server userCommand("mdelete testmf.*"); // and locally removeFile("testmf.a"); removeFile("testmf.b"); removeFile("testmf.c");}// -------------- command line interpretation -----------------// little helperstatic string getCurDir(){ char buf[200]; if (!getCurrentDirectory(buf, 200)) { return string("(error retrieving current directory!)"); } else { return string(buf); }}void SFTPC::userCommand(char const *command){ // parse the command into words StrtokParse tok(command, " \t"); if (tok == 0) { return; // user didn't type anything } // macros for querying command // (scoped by #undefining at end of fn) #define WORDnIS(str,n) (0==strcmp(tok[n], (str))) #define CMDIS(str) WORDnIS(str,0) #define CMD(str) else if (CMDIS(str)) #define NO_ARG1 if (tok <= 1) #define ARG1IS(str) WORDnIS(str,1) #define ARG1(str) else if (ARG1IS(str)) // setting variables conveniently #define SET_VAR(var, newval) \ var = newval; \ cout << #var " is now " << var << endl /*user supplies semicolon*/ #define TOGGLE_VAR(var) SET_VAR(var, !var) // toggle if no arguments, or set to match on/off arg #define TOGGLE_ON_OFF_VAR(var) \ NO_ARG1 { \ TOGGLE_VAR(var); \ } \ ARG1("on") { \ SET_VAR(var, true); \ } \ ARG1("off") { \ SET_VAR(var, false); \ } /* no semicolon! */ // command constraints #define REQUIRE_ARGS(n) \ if (tok <= n) { \ cout << "The " << tok[0] << " command requires at least " \ << n << " arguments.\n" \ << "Try \"help " << tok[0] << "\"\n"; \ return; \ } #define ELSE_BAD_ARG1 \ else { \ cout << "Unknown argument to " << tok[0] \ << ": " << tok[1] \ << "\nTry \"help " << tok[0] << "\"\n"; \ } // convenient forms of arguments char const *arg1 = (tok<=1? NULL : tok[1]); char const *arg2 = (tok<=2? NULL : tok[2]); // convenient forms of text following some argument char const *afterCmd = (tok<=1? "" : command + tok.offset(1)); //char const *afterArg1 = (tok<=2? "" : command + tok.offset(2)); // the alias table that used to be here is now in sftpcdoc.cpp // -------------- non-protocol actions -------------- if (CMDIS("debug")) { TOGGLE_ON_OFF_VAR(printOutgoing) ARG1("1") { TOGGLE_VAR(showDiagnostics); } ARG1("breaker") { breaker(); } ARG1("dump") { dumpVariables(); } ARG1("binaryAnyway") { TOGGLE_VAR(binaryTransferAnyway); } ARG1("localCRLF") { TOGGLE_VAR(localConventionIsCRLF); } ARG1("localGlobbing") { TOGGLE_VAR(localGlobbing); } ELSE_BAD_ARG1 } CMD("passive") { TOGGLE_ON_OFF_VAR(transferPassively) ELSE_BAD_ARG1 } CMD("ascii") { SET_VAR(asciiTransfers, true); } CMD("binary") { SET_VAR(asciiTransfers, false); } CMD("type") { REQUIRE_ARGS(1) SET_VAR(asciiTransfers, ARG1IS("a")); } CMD("lcd") { char const *destDir = arg1; if (!destDir) { // with no arguments, change to home directory destDir = getenv("HOME"); if (!destDir) { cout << "Can't change to home directory because environment\n" "variable HOME is not defined.\n"; goto endOfLcd; } } if (!changeDirectory(destDir)) { cout << "Couldn't change to " << destDir << endl; } else { cout << "Current local directory changed to " << getCurDir() << endl; } endOfLcd: ; // gnu requires this (grumble grumble..) } CMD("lpwd") { cout << "Current local directory is " << getCurDir() << endl; } CMD("prompt") { TOGGLE_ON_OFF_VAR(interactivePrompting) ELSE_BAD_ARG1 } CMD("quit") { quitProgram = true; } else if (command[0] == '!') { // advance to next non-ws after '!' char const *p = command+1; while (isspace(*p)) { p++; } if (system(p) == -1) { xsyserror("system"); } } CMD("hash") { TOGGLE_ON_OFF_VAR(printHashMarks) ELSE_BAD_ARG1 } // ------------- special protocol actions -------------- CMD("sync") { if (emptyResponseQueue() == 0) { cout << "The response queue appears to be empty.\n"; } } CMD("test") { NO_ARG1 { // run all tests in all modes static DataSecurityLevel const map[3] = { DSL_CLEAR, DSL_INTEGRITY, DSL_PRIVATE }; for (int lvl=0; lvl<3; lvl++) { for (int psv=0; psv<2; psv++) { if (setDataEncryption(map[lvl])) { // only run the tests if the server supports the mode runTests(psv==1 /*passive*/); } } } } ARG1("active") { runTests(false /*passive*/); } ARG1("passive") { runTests(true /*passive*/); } ARG1("text") { testTextTransfers(); } ARG1("binary") { testBinaryTransfers(); } ARG1("multi") { testMultipleFileCmds(); } ELSE_BAD_ARG1 } CMD("quote") { checkedRequest(Request(afterCmd));
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -