I am using GhostScript API to test if a PDF is user password protected (You can only open the document if you have the password, what is different from owner protection that only protects the content against unauthorized copying or printing)
I am using this code in Qt:
const bool PDFTools::isUserProtected(const QString &iFilePath)
{
void *minst;
const QString filePath = iFilePath.toUtf8();
const std::string inputFile = QString( "%1" ).arg(filePath).toStdString();
int initiationCode = 0;
int executionCode = 0;
// GhostScript arguments
const int gsArgumentCount = 2;
char * gsArgumentValues[gsArgumentCount];
gsArgumentValues[0] = "-dNODISPLAY";
gsArgumentValues[1] = const_cast<char*>( inputFile.c_str() );
initiationCode = gsapi_new_instance(&minst, NULL);
if(initiationCode < 0)
return true;
initiationCode = gsapi_set_arg_encoding(minst, GS_ARG_ENCODING_UTF8);
if (initiationCode < 0)
{
gsapi_delete_instance(minst);
return true;
}
executionCode = gsapi_init_with_args(minst, gsArgumentCount, gsArgumentValues);
if(executionCode < 0)
{
gsapi_exit(minst);
gsapi_delete_instance(minst);
return true;
}
gsapi_exit(minst);
gsapi_delete_instance(minst);
return false;
}
When the document is protected it returns true because it can't open without password and Ghostscript returns a execution error, this is OK.
The problem is when the document it's not protected it opens the first page and keeps waiting for the return key to be pressed so it can pass to the next page.
>>showpage, press <return> to continue<<
You can add the "-dNOPAUSE" and "-dBATCH" to the gsArgumentValues but this will cause another problem. If I have really big PDF with big number of pages this will take to much time to pass through all pages only after that it will return false.
Anyone knows how can I exit the process when the Ghostscript prompts for return? At that stage I already know that the PDF is not protected and I can exit and return false.
I have tried with the callback function but with no success.
Thanks in advance.
Try using -dFirstPage=1 -dLastPage=1 and -dNOPAUSE -dBATCH. Then it only processes the first page.
Otherwise you'll have to intercept the press any key and interrupt the process, or modify the device to throw an error after the first page or something.
NOTE although you can use these switches with other types of input than PDF, they still have to process the entire input file because, unlike PDF, there is no easy way to find the n'th page in a PostScript or PCL file.
Related
In regard to Indy 10 of IdHTTP, many things have been running perfectly, but there are a few things that don't work so well here. That is why, once again, I need your help.
Download button has been running perfectly. I'm using the following code :
void __fastcall TForm1::DownloadClick(TObject *Sender)
{
MyFile = SaveDialog->FileName;
TFileStream* Fist = new TFileStream(MyFile, fmCreate | fmShareDenyNone);
Download->Enabled = false;
Urlz = Edit1->Text;
Url->Caption = Urlz;
try
{
IdHTTP->Get(Edit1->Text, Fist);
IdHTTP->Connected();
IdHTTP->Response->ResponseCode = 200;
IdHTTP->ReadTimeout = 70000;
IdHTTP->ConnectTimeout = 70000;
IdHTTP->ReuseSocket;
Fist->Position = 0;
}
__finally
{
delete Fist;
Form1->Updated();
}
}
However, a "Cancel Resume" button is still can't resume interrupted downloads. Meant, it is always sending back the entire file every time I call Get() though I've used IdHTTP->Request->Ranges property.
I use the following code:
void __fastcall TForm1::CancelResumeClick(TObject *Sender)
{
MyFile = SaveDialog->FileName;;
TFileStream* TFist = new TFileStream(MyFile, fmCreate | fmShareDenyNone);
if (IdHTTP->Connected() == true)
{
IdHTTP->Disconnect();
CancelResume->Caption = "RESUME";
IdHTTP->Response->AcceptRanges = "Bytes";
}
else
{
try {
CancelResume->Caption = "CANCEL";
// IdHTTP->Request->Ranges == "0-100";
// IdHTTP->Request->Range = Format("bytes=%d-",ARRAYOFCONST((TFist->Position)));
IdHTTP->Request->Ranges->Add()->StartPos = TFist->Position;
IdHTTP->Get(Edit1->Text, TFist);
IdHTTP->Request->Referer = Edit1->Text;
IdHTTP->ConnectTimeout = 70000;
IdHTTP->ReadTimeout = 70000;
}
__finally {
delete TFist;
}
}
Meanwhile, by using the FormatBytes function, found here, has been able to shows only the size of download files. But still unable to determine the speed of download or transfer speed.
I'm using the following code:
void __fastcall TForm1::IdHTTPWork(TObject *ASender, TWorkMode AWorkMode, __int64 AWorkCount)
{
__int64 Romeo = 0;
Romeo = IdHTTP->Response->ContentStream->Position;
// Romeo = AWorkCount;
Download->Caption = FormatBytes(Romeo) + " (" + IntToStr(Romeo) + " Bytes)";
ForSpeed->Caption = FormatBytes(Romeo);
ProgressBar->Position = AWorkCount;
ProgressBar->Update();
Form1->Updated();
}
Please advise and give an example. Any help would sure be appreciated!
In your DownloadClick() method:
Calling Connected() is useless, since you don't do anything with the result. Nor is there any guarantee that the connection will remain connected, as the server could send a Connection: close response header. I don't see anything in your code that is asking for HTTP keep-alives. Let TIdHTTP manage the connection for you.
You are forcing the Response->ResponseCode to 200. Don't do that. Respect the response code that the server actually sent. The fact that no exception was raised means the response was successful whether it is 200 or 206.
You are reading the ReuseSocket property value and ignoring it.
There is no need to reset the Fist->Position property to 0 before closing the file.
Now, with that said, your CancelResumeClick() method has many issues.
You are using the fmCreate flag when opening the file. If the file already exists, you will overwrite it from scratch, thus TFist->Position will ALWAYS be 0. Use fmOpenReadWrite instead so an existing file will open as-is. And then you have to seek to the end of the file to provide the correct Position to the Ranges header.
You are relying on the socket's Connected() state to make decisions. DO NOT do that. The connection may be gone after the previous response, or may have timed out and been closed before the new request is made. The file can still be resumed either way. HTTP is stateless. It does not matter if the socket remains open between requests, or is closed in between. Every request is self-contained. Use information provided in the previous response to govern the next request. Not the socket state.
You are modifying the value of the Response->AcceptRanges property, instead of using the value provided by the previous response. The server tells you if the file supports resuming, so you have to remember that value, or query it before then attempting to resumed download.
When you actually call Get(), the server may or may not respect the requested Range, depending on whether the requested file supports byte ranges or not. If the server responds with a response code of 206, the requested range is accepted, and the server sends ONLY the requested bytes, so you need to APPEND them to your existing file. However, if the server response with a response code of 200, the server is sending the entire file from scratch, so you need to REPLACE your existing file with the new bytes. You are not taking that into account.
In your IdHTTPWork() method, in order to calculate the download/transfer speed, you have to keep track of how many bytes are actually being transferred in between each event firing. When the event is fired, save the current AWorkCount and tick count, and then the next time the event is fired, you can compare the new AWorkCount and current ticks to know how much time has elapsed and how many bytes were transferred. From those value, you can calculate the speed, and even the estimated time remaining.
As for your progress bar, you can't use AWorkCount alone to calculate a new position. That only works if you set the progress bar's Max to AWorkCountMax in the OnWorkBegin event, and that value is not always know before a download begins. You need to take into account the size of the file being downloaded, whether it is being downloaded fresh or being resumed, how many bytes are being requested during a resume, etc. So there is lot more work involved in displaying a progress bar for a HTTP download.
Now, to answer your two questions:
How to retrieve and save the download file to a disk by using its original name?
It is provided by the server in the filename parameter of the Content-Disposition header, and/or in the name parameter of the Content-Type header. If neither value is provided by the server, you can use the filename that is in the URL you are requesting. TIdHTTP has a URL property that provides the parsed version of the last requested URL.
However, since you are creating the file locally before sending your download request, you will have to create a local file using a temp filename, and then rename the local file after the download is complete. Otherwise, use TIdHTTP.Head() to determine the real filename (you can also use it to determine if resuming is supported) before creating the local file with that filename, then use TIdHTTP.Get() to download to that local file. Otherwise, download the file to memory using TMemoryStream instead of TFileStream, and then save with the desired filename when complete.
when I click http://get.videolan.org/vlc/2.2.1/win32/vlc-2.2.1-win32.exe then the server will process requests to its actual url. http://mirror.vodien.com/videolan/vlc/2.2.1/win32/vlc-2.2.1-win32.exe. The problem is that IdHTTP will not automatically grab through it.
That is because VideoLan is not using an HTTP redirect to send clients to the real URL (TIdHTTP supports HTTP redirects). VideoLan is using an HTML redirect instead (TIdHTTP does not support HTML redirects). When a webbrowser downloads the first URL, a 5 second countdown timer is displayed before the real download then begins. As such, you will have to manually detect that the server is sending you an HTML page instead of the real file (look at the TIdHTTP.Response.ContentType property for that), parse the HTML to determine the real URL, and then download it. This also means that you cannot download the first URL directly into your target local file, otherwise you will corrupt it, especially during a resume. You have to cache the server's response first, either to a temp file or to memory, so you can analyze it before deciding how to act on it. It also means you have to remember the real URL for resuming, you cannot resume the download using the original countdown URL.
Try something more like the following instead. It does not take into account for everything mentioned above (particularly speed/progress tracking, HTML redirects, etc), but should get you a little closer:
void __fastcall TForm1::DownloadClick(TObject *Sender)
{
Urlz = Edit1->Text;
Url->Caption = Urlz;
IdHTTP->Head(Urlz);
String FileName = IdHTTP->Response->RawHeaders->Params["Content-Disposition"]["filename"];
if (FileName.IsEmpty())
{
FileName = IdHTTP->Response->RawHeaders->Params["Content-Type"]["name"];
if (FileName.IsEmpty())
FileName = IdHTTP->URL->Document;
}
SaveDialog->FileName = FileName;
if (!SaveDialog->Execute()) return;
MyFile = SaveDialog->FileName;
TFileStream* Fist = new TFileStream(MyFile, fmCreate | fmShareDenyWrite);
try
{
try
{
Download->Enabled = false;
Resume->Enabled = false;
IdHTTP->Request->Clear();
//...
IdHTTP->ReadTimeout = 70000;
IdHTTP->ConnectTimeout = 70000;
IdHTTP->Get(Urlz, Fist);
}
__finally
{
delete Fist;
Download->Enabled = true;
Updated();
}
}
catch (const EIdHTTPProtocolException &)
{
DeleteFile(MyFile);
throw;
}
}
void __fastcall TForm1::ResumeClick(TObject *Sender)
{
TFileStream* Fist = new TFileStream(MyFile, fmOpenReadWrite | fmShareDenyWrite);
try
{
Download->Enabled = false;
Resume->Enabled = false;
IdHTTP->Request->Clear();
//...
Fist->Seek(0, soEnd);
IdHTTP->Request->Ranges->Add()->StartPos = Fist->Position;
IdHTTP->Request->Referer = Edit1->Text;
IdHTTP->ConnectTimeout = 70000;
IdHTTP->ReadTimeout = 70000;
IdHTTP->Get(Urlz, Fist);
}
__finally
{
delete Fist;
Download->Enabled = true;
Updated();
}
}
void __fastcall TForm1::IdHTTPHeadersAvailable(TObject*Sender, TIdHeaderList *AHeaders, bool &VContinue)
{
Resume->Enabled = ( ((IdHTTP->Response->ResponseCode == 200) || (IdHTTP->Response->ResponseCode == 206)) && TextIsSame(AHeaders->Values["Accept-Ranges"], "bytes") );
if ((IdHTTP->Response->ContentStream) && (IdHTTP->Request->Ranges->Count > 0) && (IdHTTP->Response->ResponseCode == 200))
IdHTTP->Response->ContentStream->Size = 0;
}
#Romeo:
Also, you can try a following function to determine the real download filename.
I've translated this to C++ based on the RRUZ'function. So far so good, I'm using it on my simple IdHTTP download program, too.
But, this translation result is of course still need value improvement input from Remy Lebeau, RRUZ, or any other master here.
String __fastcall GetRemoteFileName(const String URI)
{
String result;
try
{
TIdHTTP* HTTP = new TIdHTTP(NULL);
try
{
HTTP->Head(URI);
result = HTTP->Response->RawHeaders->Params["Content-Disposition"]["filename"];
if (result.IsEmpty())
{
result = HTTP->Response->RawHeaders->Params["Content-Type"]["name"];
if (result.IsEmpty())
result = HTTP->URL->Document;
}
}
__finally
{
delete HTTP;
}
}
catch(const Exception &ex)
{
ShowMessage(const_cast<Exception&>(ex).ToString());
}
return result;
}
I'm using TaskDialogIndirect to display prompts to the user. Normally this works just fine, but sometimes, after the program has been running for a while, it begins returning an error code that the MSDN entry does not list as being one of the error codes this function can return.
0x80040001 OLE_E_ADVF "Invalid advise flags"
I have checked all the inputs to the function against previous successful calls in the same run. Aside from differences in the string to be displayed, they are identical. (the strings are even the same length.)
// create task dialog struct
TASKDIALOGCONFIG tdc;
ZeroMemory(&tdc, sizeof(TASKDIALOGCONFIG));
tdc.cbSize = sizeof(tdc);
tdc.dwFlags = (((dwMessageBoxFlags & MB_OKCANCEL) == MB_OKCANCEL) ? TDF_ALLOW_DIALOG_CANCELLATION : 0) | TDF_POSITION_RELATIVE_TO_WINDOW;
tdc.hwndParent = hwndOwner;
tdc.hInstance = LGetHInstance();
tdc.pszContent = usrText.wsz;
tdc.pButtons = _pButtons;
tdc.cButtons = nButtons;
tdc.pszMainIcon = pszTaskDialogIcon;
tdc.pszWindowTitle = usrCaption.wsz;
tdc.nDefaultButton = nDefaultButton;
// display it now
int iButton = 0;
BOOL b = 0;
HRESULT hResult = TaskDialogIndirect(&tdc, &iButton, NULL, &b);
NEW INFORMATION
At the same time that TaskDialogIndirect stops behaving correctly, ShellExecute also stops working, as does CreateFile.
This was actually caused by an event handle leak elsewhere. When the available handles ran out, no more API calls which needed to create a handle could succeed. They did return a rather odd set of error codes though, none of which was "out of handles".
I have the following problem that has me stumped. As a note to keep in mind, I am using a precompiled sqlite3.dll rather than Qt's built-in SQLite support.
Table creation code:
CREATE TABLE STUDY(
Name NOT NULL,
UserName NOT NULL REFERENCES USERS(UserName),
Description DEFAULT NULL,
PathToOsirixDB DEFAULT NULL,
PRIMARY KEY(Name, UserName)
)
The following C++ code fails to update the value in column PathToOsirixDB if it already contains a value.
It fails silently, with no error returned. That would imply that no rows are matched in the UPDATE. However, if I take the same UPDATE with valid entries for user and study to match a row and run it via either the SQLite Manager Firefox plugin, or the command line tool, it works properly.
void CStudyDatabase::SetOsirixDBForStudy(const QString user, const QString study, const QString path)
{
if (mp_db)
{
int before = sqlite3_total_changes(mp_db);
QString insert = QString("UPDATE STUDY SET PathToOsirixDB = '%1' WHERE Name = '%2' AND UserName = '%3'").arg(path, study, user);
if (!InsertData(insert))
{
int after = sqlite3_total_changes(mp_db);
if (after - before >= 1)
{
SetOsirixDB(path.toAscii().data());
emit ReturnOsirixDB(osirix_db);
}
else
{
emit DatabaseError(QString("Failed to update the target path."));
}
}
}
}
And for Insert Data
int CStudyDatabase::InsertData(const QString insert)
{
char *err_msg = 0;
int rc = sqlite3_exec(mp_db,
insert.toStdString().c_str(),
NULL,
this,
&err_msg);
if (rc)
SignalDatabaseError(&err_msg);
return rc;
}
Any insights are appreciated. Thank you.
UPDATE: added the following code to SetOsiriXDBForStudy to see if we actually find a row to update:
osirix_db = QString("");
QString query = QString("SELECT PathToOsirixDB FROM STUDY WHERE Name = '%1' AND UserName = '%2'").arg(study, user);
int rc = 0;
char *err_msg = 0;
rc = sqlite3_exec(mp_db,
query.toStdString().c_str(),
&OsirixDBCallback,
this,
&err_msg);
if (rc)
SignalDatabaseError(&err_msg);
if (!(QString(osirix_db).isEmpty()))
studyrowfound = true;
In this case, it leaves osirix_db as an empty string.
But, if I execute this function:
void CStudyDatabase::QueryOsirixDB(const QString user, const QString study)
{
int rc = 0;
char *err_msg = 0;
// we query the OrisixDB via the callback and then emit the signal if all went well
QString query = QString("SELECT PathToOsirixDB FROM STUDY WHERE Name = '%1' AND UserName = '%2'").arg(study, user);
rc = sqlite3_exec(mp_db,
query.toStdString().c_str(),
&OsirixDBCallback,
this,
&err_msg);
if (rc)
SignalDatabaseError(&err_msg);
if (QString(osirix_db).isEmpty())
emit NeedToSetOsirixDB(user, study, QString());
else
emit ReturnOsirixDB(osirix_db);
}
Then it does correctly retrieve the expected value into osirix_db.
Last update: found the problem. It was leading spaces on user and study. Had been staring at the same debugger statements for too long and glossed over the extra spaces. The failing SELECT was a big clue that there was something wrong in the construction of the SQL.
There were leading spaces on user and study. Had been staring at the same debugger statements for too long and glossed over the extra spaces. The failing SELECT was a big clue that there was something wrong in the construction of the SQL.
Hi friends I am working on an application where I should check the file system of the SD card and also I need to check whether the SD card inserted is write protected or not. I was successful in getting the file system details as follows:
TCHAR volumeName[MAX_PATH + 1] = { 0 };
TCHAR fileSystemName[MAX_PATH + 1] = { 0 };
DWORD serialNumber = 0;
DWORD maxComponentLen = 0;
DWORD fileSystemFlags = 0;
LPCWSTR path = deviceData->m_strPath.utf16(); // deviceData gives me the path of the SD Card
// Get the file system details
if (GetVolumeInformation(
path,
volumeName,
ARRAYSIZE(volumeName),
&serialNumber,
&maxComponentLen,
&fileSystemFlags,
fileSystemName,
ARRAYSIZE(fileSystemName)))
{
newData.strFileSystem = QString::fromUtf16(fileSystemName);
}
m_SDInfoList.append(newData); // m_SDInfoList is QList
With this approach I get to know whether the file system is FAT32 or NTFS. Now I want to achieve the write protected details. Is their a Qt API that can gimme whether the inserted SD Card is write protected or not??? Please help :)
Update:
This is what I did using QFileInfo:
QFileInfo fileInfo(deviceData->m_strPath);
if(!fileInfo.isWritable())
{
newData.strStatus = "WriteProtect Enabled";
}
else
{
newData.strStatus = "WriteProtect Disabled";
}
It always ends up giving me WriteProtect Disable even though I have set the write protected permission.
I suspect you can't do that with Qt. You'll need to add custom code for each target platform.
However, you can simply try to create an empty file and then immediately delete it. If creating the file fails then the volume is likely read only (or it has ran out of free space, you'll need to check the error codes to be sure).
Not sure about how to do it in QT, but WinAPI provides you with GetVolumeInformation (http://pinvoke.net/default.aspx/kernel32/GetVolumeInformation.html) method (allows you to check weather drive is write protected or not), that returns the following flag:
/// <summary>
/// The specified volume is read-only.
/// </summary>
ReadOnlyVolume = 0x80000,
I am trying to get the display name of the running service using c++. I was trying to use the GetServiceDisplayName function but it does not seem to be working, not sure why.
TTServiceBegin( const char *svcName, PFNSERVICE pfnService, bool *svc, PFNTERMINATE pfnTerm,
int flags, int argc, char *argv[], DWORD dynamiteThreadWaitTime )
{
SC_HANDLE serviceStatusHandle;
DWORD dwSizeNeeded = 0 ;
TCHAR* szKeyName = NULL ;
serviceStatusHandle=OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE ,SC_MANAGER_ALL_ACCESS);
GetServiceDisplayName(serviceStatusHandle,svcName, NULL, &dwSizeNeeded);
if(dwSizeNeeded)
{
szKeyName = new char[dwSizeNeeded+1];
ZeroMemory(szKeyName,dwSizeNeeded+1);
if(GetServiceDisplayName(serviceStatusHandle ,svcName,szKeyName,&dwSizeNeeded)!=0)
{
MessageBox(0,szKeyName,"Got the key name",0);
}
}
When i run this code, i can never see the value of szKeyName in my debugger and it goes into the if block for the message box but never displays the message box. Not sure why?
Anyway to get this to work to get the display name of the service or any other/easier way to accomplish that task?
You need to use the WTSSendMessage instead of the MessageBox to interact with the active session.
WTS_SESSION_INFO* pSessionInfo = NULL;
DWORD dwSessionsCount = 0;
if(WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwSessionsCount))
{
for(int i=0; i<(int)dwSessionsCount; i++)
{
WTS_SESSION_INFO &si = pSessionInfo[i];
if(si.State == WTSActive)
{
DWORD dwIdCurrentSession = si.SessionId;
std::string strTitle = "Hello";
std::string strMessage = "This is a message from the service";
DWORD dwMsgBoxRetValue = 0;
if(WTSSendMessage(
WTS_CURRENT_SERVER_HANDLE,
dwIdCurrentSession,
(char*)strTitle.c_str(),
strTitle.size(),
(char*)strMessage.c_str(),
strMessage.size(),
MB_RETRYCANCEL | MB_ICONINFORMATION | MB_TOPMOST,
60000,
&dwMsgBoxRetValue,
TRUE))
{
switch(dwMsgBoxRetValue)
{
case IDTIMEOUT:
// Deal with TimeOut...
break;
case IDCANCEL:
// Deal With Cancel....
break;
}
}
else
{
// Deal With Error
}
break;
}
}
WTSFreeMemory(pSessionInfo);
}
The message box will not be visible on Windows Vista and later due to a change that has services running in a separate session (Session 0 Isolation) that does not have access to a desktop so the message box would not be visible to you, the logged on user.
On Window XP and earlier, you need to tick the Allow service to interact with desktop checkbox under the Log On tab in the service's properties dialog for your service to make message box appear.
Instead, you could write the service name out to a file or run a user application that accepts the name of the service to query and have it query and display the service name (I just tried with the posted code and it works correctly, displaying the message box).