Changing last 5 char of array - c++

I have a program that encrypts files, but adds the extension ".safe" to the end. So the end result is something like "file.txt.safe"
When I go to decrypt the file, the user enters the file name again: "file.txt.safe" which is saved to a char. Now I want to remove ".safe" and rename the file to its original name.
I have tried the following, but nothing seems to happen and there are no errors.
Decrypt (myFile); //decrypts myFile
char * tmp = myFile;
char * newFile;
newFile = strstr (tmp,".safe"); //search tmp for ".safe"
strncpy (newFile,"",5); //replace .safe with ""
rename (myFile, newFile);
I'm sure I'm missing something obvious, but if this approach doesn't work, I'm looking for any simple method.
Edited to add:
(copied by moderator from poster's response to K-ballo)
Thanks everyone. I took the std::string approach and found this to work:
Decrypt(myFile);
string str = myFile;
size_t pos = str.find(".safe");
str.replace(pos,5,"");
rename(myFile, str.c_str());

For what you want to do, simply changing the strncpy line to this will work:
*newFile = '\0';
This would still have problems if the filename contains an early .safe (like in file.safest.txt.safe), or if it does not contain the substring .safe at all. You would be better of searching from the end of the array, and making sure you do find something.
This seems like a better approach (although in C++ it would be better to just go with std::string):
char* filename = ...;
size_t filename_length = strlen( filename );
int safe_ext_pos = filename_length - 5; // 5 == length of ".safe"
if( safe_ext_pos > 0 && strcmp( ".safe", filename + safe_ext_pos ) == 0 )
filename[ safe_ext_pos ] = '\0';
This is the std::string version of the code:
std::string filename = ...;
int safe_ext_pos = filename.length() - 5; // 5 == length of ".safe"
if( safe_ext_pos > 0 && filename.compare( safe_ext_pos, 5, ".safe" ) == 0 )
filename.erase( safe_ext_pos );

You should take care:
my.safe.file.txt.safe
Instead of just searching for '.safe' and removing it or truncating the filename at the first one, you should ensure that it's actually at the end of the string:
std::string myfile = ...
Decrypt(myFile);
const std::string extension_to_remove = ".safe";
if (decryption is successful &&
myfile.size() >= extension_to_remove.size() &&
myfile.substr(myfile.size()-5) == extension_to_remove)
{
std::string newFile = myfile.substr(0, myfile.size()-5);
rename(myFile, newFile);
}
Also a note on filename extensions. It's really a pretty awful practice for software to identify file types using a special format in the filename.* It's fine for humans to organize their files with special naming conventions, but software should by and large be oblivious to it, except perhaps to make it easy for humans to use the conventions they want.
So your code for decrypting a file shouldn't be doing this task. Instead your decryption code should take a file to decrypt and a file to contain the output. Then your code for computing the output filename from the encrypted file's name should exist somewhere else, such as in the user interface where the user tells you the output filename. Your code would remove '.safe' if it exists and supply the modified name as the default output filename, to be confirmed by the user.
void perform_decryption(std::string const &encrypted, std::string const &decrypted) {
Decrypt(encrypted);
if (decryption is successful && encrypted!=decrypted)
rename(encrypted, decrypted);
}
std::string default_decrypted_name(std::string const &filename) {
const std::string extension_to_remove = ".safe";
if (filename.size() >= extension_to_remove.size() &&
filename.substr(filename.size()-extension_to_remove.size()) == extension_to_remove)
{
return filename.substr(0, filename.size()-extension_to_remove.size());
}
return filename + ".decrypted";
}
* here are some reasons against filename extensions:
filename extensions are not unique, in some circumstances causing conflicts where a file's type cannot be positively identified. (the fact that they can't even perform their intended purpose really ought to be enough...)
It degrades the usability of the filename for organizing. When 'myfile.txt' is renamed to 'myfile.txt.old' it's no longer seen as a text file.
It's caused security issues because fake type metadata can be mistaken for real type metadata when the real type metadata is hidden.
and more...

Related

How do I use std::rename with variables?

In my program, I store data in different text files. The data belongs to an object, which I call rockets. For example, the rocket Saturn 5 has a text file labeled "Saturn5R.txt". I want an option to rename the rocket, and so I will need to rename the text file as well. I am using std::rename in the library. I have gotten it working with something like this:
char oldname[] = "Saturn5R.txt";
char newname[] = "Saturn6R.txt";
if (std::rename(oldname, newname) != 0) {
perror("Error renaming file");
}
This works, but I don't want to always be renaming Saturn5R.txt to Saturn6R.txt. What I want to do is to be able to rename any text file to any name, I have tried this and I get an error:
char oldname[] = rocketName + RocketNumber + "R.txt";
char newname[] = NameChoice + NumberChoice + "R.txt";
if (std::rename(oldname, newname) != 0) {
perror("Error renaming file");
}
This returns the error "[cquery] array initializer must be an initializer list or string literal".
How can I use std::rename or any other file renaming function that allows me to rename any files I want without hardcoding them in?
This has little to do with std::rename, and everything to do with how to interpolate variables into a string. A simple solution is to use std::string. It has overloaded operator + that can be used to concatenate substrings.
If you want to make the program a bit fancier, C++20 added std::format:
std::string oldname = std::format("{}{}R.txt", rocketName, RocketNumber);
std::string newname = std::format("{}{}R.txt", NameChoice, NumberChoice);
if (std::rename(oldname.c_str(), newname.c_str()) != 0) {
P.S. I recommend using std::filesystem::rename instead since it has better ways of handling errors in my opinion.

Extracting the immediate parent directory from a file path using boost

Assuming I have
String t = "c:/foo/foo1/foo2/foo3/file.txt"
I want to extract "foo3/file.txt".
How can I do this (using boost or std)?
Here is what I've been trying to so far:
boost::filesystem::path pathToObject(t);
Using pathToObject.filename() I can extract the file name of course. And I've played around with t.find_last_of("/") but I really need like t.find_second_to_last_of("/").
string::find_last_of has an optional argument which lets you specify how far into the string you are looking.
So you can define
size_t second_to_last = t.find_last_of("/", t.find_last_of("/")-1);
std::string file_with_parent = t.substr(second_to_last+1);
The second argument tells him to only search before the last /.
WARNING: This might differ from what you want if you have stuff like "C:/bli/bla//blubb.txt". In general, paths can be complex and confusing and trying to conquer them with string manipulation will only work for very well-behaved input, which one usually can't assume.
I therefore recommended using a proper tool for this job.* But since the question claimed that find_last_of wouldn't do the job I felt quite compelled to remind people that the standard facilities are not entirely as impotent as many seem to believe them to be.
*I suspect the boost path lib to be one but I have never worked with it.
It is rather odd to extract a path like that. Maybe you are looking for a relative path? boost filesystem has a tool for that. Be sure to give the documentation a good look over. But to answer your question:
namespace bfs= boost::filesystem;
int main( ) {
bfs::path path( "c:/foo/foo1/foo2/foo3/file.txt" );
bfs::path target( path );
target.remove_filename( );
target= target.filename( ) / path.filename( );
std::cout << target << std::endl;
}
I don't have a compiler handy to test it, but based on the example here, this code should basically work or point you in about the right direction. It could probably be simplified a little bit even from what I've written here.
path p1( "c:/foo/foo1/foo2/foo3/file.txt" );
path p2;
for (path::iterator it(p1.end()), int i = 0; it != p1.begin() && i < 2; --it, ++i) {
path temp = p2;
p2 = it;
p2 /= temp;
}
Here is the solution I ended up using:
std::string t = pathObject.parent_path().filename().string();
t.append("/");
t.append(pathObject.filename().string());
Using parent_path gave me just the path. Then I used the filename to extract just the directory. Then I appended the filename of the child directory.
Following method return immediate parent directory.
#include <string>
string getParentDirectory(string& filePath)
{
if (filePath.empty() == false)
{
size_t toPos = filePath.find_last_of('\\') - 1;
if (toPos != string::npos)
{
size_t fromPos = filePath.find_last_of('\\', toPos);
if (fromPos != string::npos)
{
return filePath.substr(fromPos + 1, toPos - fromPos);
}
}
}
return "";
}
int main()
{
string str = "D:\\Devs\\Test\\sprite.png";
string parentDir = getParentDirectory(str);
return 0;
}
It prints the value of parentDir is "Test".

C++ strtok - multiple use with more data buffers

I have little issue with using strtok() function.
I am parsing two files. Firts I load file 1 into buffer. This file constains name of the second file I need to load. Both files are read line after line. My code looks like this:
char second_file_name[128] = { "" };
char * line = strtok( buffer, "\n" );
while( line != NULL )
{
if ( line[0] = 'f' )
{
sscanf( line, "%*s %s", &second_file_name );
LoadSecondFile( second_file_name );
}
// processing other lines, not relevant for question
line = strtok( NULL, "\n" );
}
While the LoadSecondFile(...) function works in pretty same way, thus:
char * line = strtok( buffer, "\n" );
while( line != NULL )
{
// process file data
line = strtok( NULL, "\n" );
}
What my problem is, after calling the LoadSecondFile(...) function, the strtok() pointer used for parsing the first file gets "messed up". Instead of giving me line that follows the name of the second file, it gives me nothing - understand as "complete nonsense". Do I get it right that this is caused by strtok() pointer being shared in program, not only in function? If so, how can I "back up" the pointer of strtok() used for parsing first file before using it for parsing second file?
Thanks for any advice.
Cheers.
strtok is an evil little function which maintains global state, so (as you've found) you can't tokenise two strings at the same time. On some platforms, there are less evil variants with names like strtok_r or strtok_s; but since you're writing C++ not C, why not use the C++ library?
ifstream first_file(first_file_name); // no need to read into a buffer
string line;
while (getline(first_file, line)) {
if (!line.empty() && line[0] == 'f') { // NOT =
istringstream line_stream(line);
string second_file_name;
line_stream.ignore(' '); // skip first word ("%*s")
line_stream >> second_file_name; // read second word ("%s")
LoadSecondFile(second_file_name);
}
}
You can use strtok_r which allows you to have different state pointers.
Which is why it is constantly recommended to not use strtok
(not to mention the problems with threads). There are many
better solutions, using the functions in the C++ standard
library. None of which modify the text they're working on, and
none of which use hidden, static state.

Help me translate Python code which replaces an extension in file name to C++

I apologize if you know nothing about Python, however, the following snippet should be very readable to anyone. The only trick to watch out for - indexing a list with [-1] gives you the last element if there is one, or raises an exception.
>>> fileName = 'TheFileName.Something.xMl'
>>> fileNameList = fileName.split('.')
>>> assert(len(fileNameList) > 1) # Must have at least one period in it
>>> assert(fileNameList[-1].lower() == 'xml')
>>> fileNameList[-1] = 'bak'
>>> fileName = '.'.join(fileNameList)
>>> print(fileName)
TheFileName.Something.bak
I need to convert this logic into C++ (the language I am actually using, but so far suck at) function with the following signature: void PopulateBackupFileNameOrDie(CAtlString& strBackupFileName, CAtlString& strXmlFileName);. Here strXmlFileName is "input", strBackupFileName is "output" (should I reverse the oprder of the two?). The tricky part is that (correct me if I am wrong) I am working with a Unicode string, so looking for these characters: .xmlXML is not as straight-forward. Latest Python does not have these issues because '.' and "." are both Unicode strings (not a "char" type) of length 1, both contain just a dot.
Notice that the return type is void - do not worry much about it. I do not want to bore you with details of how we communicate an error back to the user. In my Python example I just used an assert. You can do something like that or just include a comment such as // ERROR: [REASON].
Please ask if something is not clear. Suggestions to use std::string, etc. instead of CAtlString for function parameters are not what I am looking for. You may convert them inside the function if you have to, but I would prefer not mixing different string types in one function. I am compiling this C++ on Windows, using VS2010. This implies that I WILL NOT install BOOST, QTString or other libraries which are not available out of the box. Stealing a boost or other header to enable some magic is also not the right solution.
Thanks.
If you're using ATL why not just use CAtlString's methods?
CAtlString filename = _T("TheFileName.Something.xMl");
//search for '.' from the end
int dotIdx = filename.ReverseFind( _T('.') );
if( dotIdx != -1 ) {
//extract the file extension
CAtlString ext = filename.Right( filename.GetLength() - dotIdx );
if( ext.CompareNoCase( _T(".xml" ) ) == 0 ) {
filename.Delete( dotIdx, ext.GetLength() ); //remove extension
filename += _T(".bak");
}
}
I didn't split the string as your code does because that's a bit more work in C++ for really no gain (it's slower, and for this task you really don't need to do it).
string filename = "TheFileName.Something.xMl";
size_t pos = filename.rfind('.');
assert(pos > 0 && pos == filename.length()-4); // the -4 here is for length of ".xml"
for(size_t i = pos+1; i < filename.length(); ++i)
filename[i] = tolower(filename[i]);
assert(filename.substr(pos+1) == "xml");
filename = filename.substr(0,pos+1) + "bak";
std::cout << filename << std::endl;

What is the easiest way to parse an INI File in C++? [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
Improve this question
I'm trying to parse an INI file using C++. Any tips on what is the best way to achieve this? Should I use the Windows API tools for INI file processing (with which I am totally unfamiliar), an open-source solution or attempt to parse it manually?
You can use the Windows API functions, such as GetPrivateProfileString() and GetPrivateProfileInt().
If you need a cross-platform solution, try Boost's Program Options library.
I have never parsed ini files, so I can't be too specific on this issue.
But i have one advice:
Don't reinvent the wheel as long as an existing one meets your requirements
http://en.wikipedia.org/wiki/INI_file#Accessing_INI_files
http://sdl-cfg.sourceforge.net/
http://sourceforge.net/projects/libini/
http://www.codeproject.com/KB/files/config-file-parser.aspx
Good luck :)
If you are already using Qt
QSettings my_settings("filename.ini", QSettings::IniFormat);
Then read a value
my_settings.value("GroupName/ValueName", <<DEFAULT_VAL>>).toInt()
There are a bunch of other converter that convert your INI values into both standard types and Qt types. See Qt documentation on QSettings for more information.
I use SimpleIni. It's cross-platform.
this question is a bit old, but I will post my answer. I have tested various INI classes (you can see them on my website) and I also use simpleIni because I want to work with INI files on both windows and winCE.
Window's GetPrivateProfileString() works only with the registry on winCE.
It is very easy to read with simpleIni. Here is an example:
#include "SimpleIni\SimpleIni.h"
CSimpleIniA ini;
ini.SetUnicode();
ini.LoadFile(FileName);
const char * pVal = ini.GetValue(section, entry, DefaultStr);
inih is a simple ini parser written in C, it comes with a C++ wrapper too. Example usage:
#include "INIReader.h"
INIReader reader("test.ini");
std::cout << "version="
<< reader.GetInteger("protocol", "version", -1) << ", name="
<< reader.Get("user", "name", "UNKNOWN") << ", active="
<< reader.GetBoolean("user", "active", true) << "\n";
The author has also a list of existing libraries here.
Have you tried libconfig; very JSON-like syntax. I prefer it over XML configuration files.
I ended up using inipp which is not mentioned in this thread.
https://github.com/mcmtroffaes/inipp
Was a MIT licensed header only implementation which was simple enough to add to a project and 4 lines to use.
If you are interested in platform portability, you can also try Boost.PropertyTree. It supports ini as persistancy format, though the property tree my be 1 level deep only.
Unless you plan on making the app cross-platform, using the Windows API calls would be the best way to go. Just ignore the note in the API documentation about being provided only for 16-bit app compatibility.
I know this question is very old, but I came upon it because I needed something cross platform for linux, win32... I wrote the function below, it is a single function that can parse INI files, hopefully others will find it useful.
rules & caveats:
buf to parse must be a NULL terminated string. Load your ini file into a char array string and call this function to parse it.
section names must have [] brackets around them, such as this [MySection], also values and sections must begin on a line without leading spaces. It will parse files with Windows \r\n or with Linux \n line endings. Comments should use # or // and begin at the top of the file, no comments should be mixed with INI entry data. Quotes and ticks are trimmed from both ends of the return string. Spaces are only trimmed if they are outside of the quote. Strings are not required to have quotes, and whitespaces are trimmed if quotes are missing. You can also extract numbers or other data, for example if you have a float just perform a atof(ret) on the ret buffer.
// -----note: no escape is nessesary for inner quotes or ticks-----
// -----------------------------example----------------------------
// [Entry2]
// Alignment = 1
// LightLvl=128
// Library = 5555
// StrValA = Inner "quoted" or 'quoted' strings are ok to use
// StrValB = "This a "quoted" or 'quoted' String Value"
// StrValC = 'This a "tick" or 'tick' String Value'
// StrValD = "Missing quote at end will still work
// StrValE = This is another "quote" example
// StrValF = " Spaces inside the quote are preserved "
// StrValG = This works too and spaces are trimmed away
// StrValH =
// ----------------------------------------------------------------
//12oClocker super lean and mean INI file parser (with section support)
//set section to 0 to disable section support
//returns TRUE if we were able to extract a string into ret value
//NextSection is a char* pointer, will be set to zero if no next section is found
//will be set to pointer of next section if it was found.
//use it like this... char* NextSection = 0; GrabIniValue(X,X,X,X,X,&NextSection);
//buf is data to parse, ret is the user supplied return buffer
BOOL GrabIniValue(char* buf, const char* section, const char* valname, char* ret, int retbuflen, char** NextSection)
{
if(!buf){*ret=0; return FALSE;}
char* s = buf; //search starts at "s" pointer
char* e = 0; //end of section pointer
//find section
if(section)
{
int L = strlen(section);
SearchAgain1:
s = strstr(s,section); if(!s){*ret=0; return FALSE;} //find section
if(s > buf && (*(s-1))!='\n'){s+=L; goto SearchAgain1;} //section must be at begining of a line!
s+=L; //found section, skip past section name
while(*s!='\n'){s++;} s++; //spin until next line, s is now begining of section data
e = strstr(s,"\n["); //find begining of next section or end of file
if(e){*e=0;} //if we found begining of next section, null the \n so we don't search past section
if(NextSection) //user passed in a NextSection pointer
{ if(e){*NextSection=(e+1);}else{*NextSection=0;} } //set pointer to next section
}
//restore char at end of section, ret=empty_string, return FALSE
#define RESTORE_E if(e){*e='\n';}
#define SAFE_RETURN RESTORE_E; (*ret)=0; return FALSE
//find valname
int L = strlen(valname);
SearchAgain2:
s = strstr(s,valname); if(!s){SAFE_RETURN;} //find valname
if(s > buf && (*(s-1))!='\n'){s+=L; goto SearchAgain2;} //valname must be at begining of a line!
s+=L; //found valname match, skip past it
while(*s==' ' || *s == '\t'){s++;} //skip spaces and tabs
if(!(*s)){SAFE_RETURN;} //if NULL encounted do safe return
if(*s != '='){goto SearchAgain2;} //no equal sign found after valname, search again
s++; //skip past the equal sign
while(*s==' ' || *s=='\t'){s++;} //skip spaces and tabs
while(*s=='\"' || *s=='\''){s++;} //skip past quotes and ticks
if(!(*s)){SAFE_RETURN;} //if NULL encounted do safe return
char* E = s; //s is now the begining of the valname data
while(*E!='\r' && *E!='\n' && *E!=0){E++;} E--; //find end of line or end of string, then backup 1 char
while(E > s && (*E==' ' || *E=='\t')){E--;} //move backwards past spaces and tabs
while(E > s && (*E=='\"' || *E=='\'')){E--;} //move backwards past quotes and ticks
L = E-s+1; //length of string to extract NOT including NULL
if(L<1 || L+1 > retbuflen){SAFE_RETURN;} //empty string or buffer size too small
strncpy(ret,s,L); //copy the string
ret[L]=0; //null last char on return buffer
RESTORE_E;
return TRUE;
#undef RESTORE_E
#undef SAFE_RETURN
}
How to use... example....
char sFileData[] = "[MySection]\r\n"
"MyValue1 = 123\r\n"
"MyValue2 = 456\r\n"
"MyValue3 = 789\r\n"
"\r\n"
"[MySection]\r\n"
"MyValue1 = Hello1\r\n"
"MyValue2 = Hello2\r\n"
"MyValue3 = Hello3\r\n"
"\r\n";
char str[256];
char* sSec = sFileData;
char secName[] = "[MySection]"; //we support sections with same name
while(sSec)//while we have a valid sNextSec
{
//print values of the sections
char* next=0;//in case we dont have any sucessful grabs
if(GrabIniValue(sSec,secName,"MyValue1",str,sizeof(str),&next)) { printf("MyValue1 = [%s]\n",str); }
if(GrabIniValue(sSec,secName,"MyValue2",str,sizeof(str),0)) { printf("MyValue2 = [%s]\n",str); }
if(GrabIniValue(sSec,secName,"MyValue3",str,sizeof(str),0)) { printf("MyValue3 = [%s]\n",str); }
printf("\n");
sSec = next; //parse next section, next will be null if no more sections to parse
}
Maybe a late answer..But, worth knowing options..If you need a cross-platform solution , definitely you can try GLIB,, its interesting.. (https://developer.gnome.org/glib/stable/glib-Key-value-file-parser.html)