// // Copyright 2000 Perforce Software. All rights reserved. // // This file is part of Perforce - the FAST SCM System. // // p4wPane: // A pane (element in a view) in the p4w interface. // ------------------------------------- // Includes // #include <p4wp4.h> #include "p4wStrBuf.h" #include "p4wHtml.h" #include "p4wI18n.h" #include "p4wPane.h" #include "msgserver.h" // ------------------------------------- // Constructors and destructor. // p4wPane::p4wPane(p4wView & ParentView, p4wRequest & Request) : fView(ParentView), fRequest(Request), fSpec(NULL), fSeenOutputStat(0), fHadFatalError(0), fPrintedError(0), fFirstErrorOnly(1), fLimitExceeded(0), fDiffsFound(0), fWRootFound(0), fPromptedPW(0) { } p4wPane::~p4wPane() { if( fSpec != NULL ) delete fSpec; } // ------------------------------------- // p4 response functions. // void p4wPane::InputData(StrBuf * strbuf, Error * e) { // // Get the specString. StrPtr *specString = varList->GetVar( StrRef("specdef") ); if( specString == NULL ) return; if( p4debug.GetLevel( DT_NET ) >= 3 ) printf("specString = \"%s\"\n", specString->Text()); // // Get the Spec and process the form using it. If it's empty // render an error Spec spec(specString->Text(), "", e); if( !fRequest.ProcessForm(spec) ) { RenderError("Form contains no data.", 1); fHadFatalError = 1; return; } // // Put the form into a spec. StrBuf *specBuf = spec.Format(&fRequest.GetPostArgs()); *strbuf = *specBuf; delete specBuf; if( p4debug.GetLevel( DT_NET ) >= 3 ) printf("strbuf = \"%s\"\n", strbuf->Text()); } void p4wPane::OutputStat(StrDict * varList) { // // Generate a ::Begin() call if necessary. if( !fSeenOutputStat ) { SetVarList( varList ); // stupid hack because passing to Begin() causes many problems Begin(); fSeenOutputStat = 1; } // Clear the previous specdef if we have one. if( fSpec != NULL ) { delete fSpec; } fSpec = new Spec; // Generate a decoded version of the spec definition // IF the specdef is available SpecDataTable specData; Error e; StrPtr *specString = varList->GetVar( StrRef("specdef") ); if( specString != NULL ) { fSpec->Decode( specString, &e ); } // // Parse the spec manually if necessary. StrPtr *data = varList->GetVar( StrRef("data") ); if( data != NULL && specString != NULL ) { // Parse into the StrDict of the spec data table fSpec->ParseNoValid( data->Text(), &specData, &e ); // Now dump the StrDict, just to show it has the results // i = SpecElem of Spec // j = for element values that are lists // Debug purposes only. if( p4debug.GetLevel( DT_NET ) >= 3 ) { for( int i = 0; i < fSpec->Count(); i++ ) { StrPtr *p; SpecElem *se = fSpec->Get( i ); p = specData.Dict()->GetVar(se->tag); if( p != NULL) printf("%s: %s\n", se->tag.Text(), p->Text() ); for( int j = 0; (p = specData.Dict()->GetVar(se->tag, j)) != NULL; j++ ) printf("%s: %s\n", se->tag.Text(), p->Text() ); } printf("specString = %s\n", specString->Text()); } // // Reorder spec fields in manner dictated by the server. // This is only available in 2003.1+ servers. const StrPtr *server = fRequest.GetProtocol( "server2" ); if( server && server->Atoi() > 15 ) ReorderSpec( specString ); // Render using our constructed StrDict Render(specData.Dict()); } else { // Debug purposes only if( p4debug.GetLevel( DT_NET ) >= 3 ) { for( int k = 0; k < fSpec->Count(); k++ ) { StrPtr *s; SpecElem *se = fSpec->Get( k ); s = varList->GetVar(se->tag); if( s != NULL) printf("%s: %s\n", se->tag.Text(), s->Text() ); for( int l = 0; (s = varList->GetVar(se->tag, l)) != NULL; l++ ) printf("%s: %s\n", se->tag.Text(), s->Text() ); } // Display the specString (if we have one). if( specString != NULL ) printf("specString = %s\n", specString->Text()); } // Render with the passed-in dict. Render(varList); } // Clear the specdef. delete fSpec; fSpec = NULL; } void p4wPane::OutputText(const char *data, int len) { // // Generate a ::Begin() call if necessary. if( !fSeenOutputStat ) { Begin(); fSeenOutputStat = 1; } if( len > 0 ) { RenderText((char *)data); } } void p4wPane::OutputBinary(const char *data, int len) { // // Generate a ::Begin() call if necessary. if( !fSeenOutputStat ) { Begin(); fSeenOutputStat = 1; } if( len > 0 ) { RenderBinary((char *)data, len); } } void p4wPane::Finished() { // // Generate a ::Begin() call if necessary. if( !fSeenOutputStat ) { Begin(); fSeenOutputStat = 1; } // // Generate a ::End() call. End(); // // Reset (in case we are going to process more than one command). fSeenOutputStat = 0; } void p4wPane::OutputInfo(char level, const char *data) { // // Generate a ::Begin() call if necessary. if( !fSeenOutputStat ) { Begin(); fSeenOutputStat = 1; } RenderInfo((char *)data, level); } void p4wPane::OutputError(const char * errBuf) { // // Generate a ::Begin() call if necessary. if( !fSeenOutputStat ) { Begin(); fSeenOutputStat = 1; } StrPtr *server = fRequest.GetProtocol( "server2" ); if( !server || server->Atoi() < 8 ) { RenderError((char *) "P4web requires Perforce server version of 99.2 or above", 1); fHadFatalError = 1; } else { RenderError((char *)errBuf, 1); } } void p4wPane::HandleError(Error *err) { // // Default error processing: output all errors without // special filtering any out DoHandleError( err, 0 ); } void p4wPane::DoHandleError(Error *err, int ignoreMostErrors) { // // By default show all errors (except EV_NONE). If ignoreMostErrors // is set, then only show E_FAILED and E_FATAL errors. // // Generate a ::Begin() call if necessary. if( !fSeenOutputStat ) { Begin(); fSeenOutputStat = 1; } StrBuf errMsg; err->Fmt(&errMsg); p4wHtml newErr(1); p4wURL urlMaker; if( err->GetSeverity() == E_FAILED ) { StrPtr *server = fRequest.GetProtocol("server2"); switch( err->GetGeneric() ) { case EV_USAGE: { if( !server || server->Atoi() < 8 ) { RenderError("P4web requires Perforce server version of 99.2 or above", 1); fHadFatalError = 1; return; } // // Test for password error char *uerr1 = strstr( errMsg.Text(), "p4 login" ); char *uerr2 = strstr( errMsg.Text(), "password" ); if( !uerr2 ) uerr2 = strstr( errMsg.Text(), "Password" ); // // Password needs to be changed: generate error // with link to change password if( uerr2 ) { fHadFatalError = 1; promptPassword( errMsg.Text(), 1 ); // // User needs to login again: generate error // with link to login } else if( uerr1 ) { fHadFatalError = 1; promptPassword( errMsg.Text() ); // // Not a password error so proceed normally } else { RenderError(errMsg.Text(), 1); } } break; case EV_NONE: break; case EV_UNKNOWN: { // // Replace client unknown error with our // text plus an url to create new client. Don't ignore // this error. char *r = strstr( errMsg.Text(), "use 'client' command" ); if( r ) { newErr.Set( errMsg.Text(), r - errMsg.Text() ); StrBuf newPath; StrBuf clientURL; fRequest.UseNewBase(newPath, NULL, "path", NULL); if (fRequest.isLocalRequest() || SEC_ALLOW_CREATE_CLIENTS) { urlMaker.ConstructURL( clientURL, newPath.Text(), AC_EDITCLIENT, NULL, fRequest.GetUnicode() ); newErr.text( "either " ); newErr.beginLink( clientURL.Text() ); newErr.text( "create it" ); newErr.endLink(); newErr.text( " or " ); } else newErr.text( "please "); urlMaker.ConstructURL( clientURL, newPath.Text(), AC_CLIENTS, NULL, fRequest.GetUnicode() ); newErr.beginLink( clientURL.Text() ); newErr.text( "choose an existing client" ); newErr.endLink(); newErr.text( "." ); StrBuf fNewURL; StrBufDict args; args.SetVar( "err", "1" ); urlMaker.ConstructLocationURL( fNewURL, NULL, AC_MULTIUSER, &args, fRequest.GetUnicode() ); StrBuf location; if( fRequest.GetJavascriptMode() == 2 ) location << p4wStrBuf().NormalizeBase( fRequest.GetBase(), 0 ); else location << p4wStrBuf().NormalizeBase( fRequest.GetBase(), fRequest.GetUnicode() ); location << fNewURL; newErr << crlf << "<script language=javascript>" << crlf; newErr << "window.location = '" << location << "'" << crlf; newErr << "</script>"; fHadFatalError = 1; RenderError( newErr.Text(), 0 ); return; } // // Handle password errors char *unerr1 = strstr( errMsg.Text(), "User" ); char *unerr2 = strstr( errMsg.Text(), "doesn't exist" ); // // User needs to login again: generate error // with link to login if( unerr1 && unerr2 ) { fHadFatalError = 1; promptPassword( errMsg.Text() ); } else if( !ignoreMostErrors ) { RenderError( errMsg.Text(), 1 ); } } break; case EV_UPGRADE: case EV_FAULT: case EV_CLIENT: case EV_ADMIN: case EV_COMM: fHadFatalError = 1; RenderError(errMsg.Text(), 1); break; case EV_CONFIG: { fHadFatalError = 1; // // Replace password error with specific instructions // for recovery char *e = strstr( errMsg.Text(), "Perforce password (P4PASSWD)" ); // // Password error when p4web is running in one // of the modes which doesn't prompt the user for // a password. Tell the user that the p4web must // be restarted. if( e && fRequest.BypassAuth() ) { newErr.Set( "P4Web " ); newErr << "has encountered a Perforce "; newErr << "password error."; newErr.linebreak(); newErr << "Please contact your "; newErr << "P4Web administrator to "; newErr << "resolve this problem."; RenderError( newErr.Text(), 0 ); return; // // Password error when user was prompted for // a password. Allow the user to re-enter // username and password. } else if( e ) { promptPassword( errMsg.Text() ); // // Not a password error: generate p4 // error as usual. } else { RenderError(errMsg.Text(), 1); } } break; case EV_ILLEGAL: { // // Check for password/login errors char *ierr1 = strstr( errMsg.Text(), "please login again" ); char *ierr2 = strstr( errMsg.Text(), "Password should be" ); if( !ierr1 && !ierr2 ) ierr1 = strstr( errMsg.Text(), "Password invalid" ); // // Password needs to be changed: generate error // with link to change password if( ierr2 ) { fHadFatalError = 1; promptPassword( errMsg.Text(), 1 ); // // User needs to login again: generate error // with link to login } else if( ierr1 ) { fHadFatalError = 1; promptPassword( errMsg.Text() ); // // Not a password error so proceed normally } else { RenderError(errMsg.Text(), 1); } } break; default: RenderError(errMsg.Text(), 1); break; } } else if( err->GetSeverity() == E_FATAL ) { fHadFatalError = 1; RenderError(errMsg.Text(), 1); } else if( !ignoreMostErrors ) { RenderError(errMsg.Text(), 1); } else { IgnoreError(errMsg.Text()); } } // --------------------------------- // ------------------------------------- // ClientUser overrides. // void p4wPane::Diff(FileSys *f1, FileSys *f2, int doPage, char *df, Error *e) { if( !f1->IsTextual() || !f2->IsTextual() ) { if( f1->Compare( f2, e ) ) OutputInfo( 0, "(... files differ ...)\n" ); return; } const char *diff; CharSetCvt *cvt; CharSetCvt::CharSet cs; // // If this process is an NT service, get the P4DIFF value // from the services registry area #ifdef OS_NT if( fRequest.IsNTService() ) { enviro->BeServer( fRequest.GetSvcName() ); } #endif if( !( diff = enviro->Get( "P4DIFF" ) ) ) diff = getenv( "DIFF" ); if( !diff || !fRequest.isLocalRequest() ) { ::Diff d; StrBuf buf; FileSys *t; // // If we are in unicode mode, we need to convert the // diff text to UTF-8 so that it is displayed correctly // on the page. if( Unicode() || f1->GetType() == FST_UTF16 ) { t = FileSys::CreateGlobalTemp( FST_UNICODE ); cs = CharSetCvt::Lookup( fRequest.GetClientApi().GetCharset().Text() ); cvt = CharSetCvt::FindCvt( cs, CharSetCvt::UTF_8 ); t->Translator( cvt ); } else { t = FileSys::CreateGlobalTemp( f1->GetType() ); } // diff expects to read files in raw mode, we must // create new FileSys to allow this. FileSys *f1_bin = FileSys::Create( FST_BINARY ); f1_bin->Set( f1->Name() ); FileSys *f2_bin = FileSys::Create( FST_BINARY ); f2_bin->Set( f2->Name() ); d.SetInput( f1_bin, f2_bin, df, e ); if( !e->Test() ) d.SetOutput( t->Name(), e ); if( !e->Test() ) d.DiffWithFlags( df ); if( !e->Test() ) d.CloseOutput( e ); if( !e->Test() ) t->Open( FOM_READ, e ); if( !e->Test() ) t->ReadWhole( &buf, e ); if( !e->Test() ) t->Close( e ); delete t; if( Unicode() ) delete cvt; OutputText( buf.Text(), buf.Length() ); return; } // // Run P4DIFF program if specified if( !df || !*df ) { ++fDiffsFound; RunCmd( diff, f1->Name(), f2->Name(), 0, 0, 0, 0, e ); } else { StrBuf flags; flags.Set( "-", 1 ); flags << df; RunCmd( diff, flags.Text(), f1->Name(), f2->Name(), 0, 0, 0, e ); } } // // Render overrides //--------------------------------- void p4wPane::RenderInfo( char *data, char level ) { // // If we are here, we usually have a protocol error. Output the protocol // error. If this is not an error, the child class must override this // method. RenderError( "P4web requires Perforce server version of 99.2 or above.", 1 ); } void p4wPane::RenderError(char *data, int escapeHTML) { // // Default method to output an error. By default we // generate an error from within a table p4wHtml htm; if( !fFirstErrorOnly || !fPrintedError ) { RenderErrorTbl( data, escapeHTML ); fPrintedError = 1; } } void p4wPane::RenderErrorTbl(char *data, int escapeHTML) { // // Output an error from within a table p4wHtml htm; p4wURL urlMaker; StrBuf clearIcon; urlMaker.ConstructURL( clearIcon, "/clearpixelIcon", AC_ICON, NULL ); if( !fFirstErrorOnly || !fPrintedError ) { // // A little vertical space above the error htm.beginTRow(); htm.beginCol(); htm.icon( clearIcon.Text(), "5", "0", "", 1 ); htm.endCol(); htm.endTRow(); // // Span the error over several columns htm.beginTRow(); htm.beginCol( NULL, NULL, "6", NULL, NULL, NULL, NULL, 1 ); fRequest << htm; htm.Clear(); RenderErrorTxt( data, escapeHTML ); htm.endCol(); htm.endTRow(); // // A little vertical space underneath htm.beginTRow(); htm.beginCol(); htm.icon( clearIcon.Text(), "5", "0", "", 1 ); htm.endCol(); htm.endTRow(); fRequest << htm; fPrintedError = 1; } } void p4wPane::RenderErrorList(char *data, int escapeHTML) { // // Output an error from within a list p4wHtml htm; if( !fFirstErrorOnly || !fPrintedError ) { htm.dListVal(); fRequest << htm; RenderErrorTxt( data, escapeHTML ); fRequest << crlf; fPrintedError = 1; } } void p4wPane::RenderErrorUList(char *data, int escapeHTML) { // // Output an error from an unnumbered list p4wHtml htm; if( !fFirstErrorOnly || !fPrintedError ) { htm.listVal(); fRequest << htm; RenderErrorTxt( data, escapeHTML ); htm.Clear(); htm.endListVal(); fRequest << htm; fPrintedError = 1; } } void p4wPane::RenderErrorTxt(char *data, int escapeHTML) { // // Output the error message in red with no other formatting. // Optionally escape special HTML characters. p4wHtml htm; if( escapeHTML ) htm.text( p4wStrBuf().EscapeHTML(StrRef(data),Unicode()).Text(), NULL, NULL, "red" ); else htm.text( data, NULL, NULL, "red" ); if (strstr(data, " has not been enabled by 'p4 protect'")) { htm << "<script language=javascript>" << crlf; htm << "window.location = \""; if (fRequest.IsHTTPS()) htm << "https://"; else htm << "http://"; htm << fRequest.GetHTTPPort().Text() << "?ac=" << AC_USERPSWD << "\""; htm << "</script>" << crlf; } fRequest << htm; } // // Utilities //------------------------ int p4wPane::OutputDirectoryHeader( char *fullPath, int isFileBrowser, int useBigText, int useDepot ) { // // Generate a file or path description in which each subdirectory is // a link to the path browser. if( !useDepot ) { return OutputDirHeaderWS( fullPath, isFileBrowser, useBigText ); } else { return OutputDirHeaderDepot( fullPath, isFileBrowser, useBigText ); } } int p4wPane::OutputDirHeaderDepot( char *fullPath, int isFileBrowser, int useBigText ) { // // Generate a file or path description in which each subdirectory is // a link to the path browser. This assumes we are using depot syntax. char *p, *n; int len; char *curDir; int skipFirstSlash = 1; StrBuf newBase; StrBuf partialPath; StrBuf cls; p4wHtml suffix; p4wHtml htm; p4wHtml slash; p4wHtml slashDots; int llen; const StrPtr *pattern = fRequest.GetStateArg( "pat" ); int needsNewBase = 0; int isRoot = 0; int nb = 0; // // Set some frequently used html strings, such as the // class type for the url, slash that spans a class, // and slash ... string that spans a class if( useBigText ) cls.Set( "bigger" ); else cls.Set( "status" ); slash.beginSpan( cls.Text() ); slash << "/"; slash.endSpan(); slashDots.beginSpan( cls.Text() ); slashDots << "/..."; slashDots.endSpan(); // // Begin the header by outputting the root directory name. // The root is always '/', and '//' is used as a link. If // this is the entire path in this case, we don't have to traverse // the rest of the path. if( useBigText ) { htm.face( "b" ); fRequest << htm; htm.Clear(); } p = fullPath + 2; if( !strcmp( fullPath, "//" ) ) { ++isRoot; suffix.beginSpan( cls.Text() ); suffix << "..."; suffix.endSpan(); if( useBigText ) suffix.endFace( "b" ); } // // Unset cdf if we are in file browser if( isFileBrowser ) { ++nb; fRequest.UseNewBase( newBase, NULL, "cdf", NULL ); } // // Switch to depot mode if we are in workspace mode if( fRequest.GetViewMode() == VM_WORKSPACE ) { if( nb ) { fRequest.UseNewBase( newBase, newBase.Text(), "md", "d" ); } else { fRequest.UseNewBase( newBase, NULL, "md", "d" ); ++nb; } fRequest.UseNewBase( newBase, newBase.Text(), "cd", "//" ); fRequest.UseNewBase( newBase, newBase.Text(), "wr", NULL ); } // // If we have changed any base args, we need to set the path if( nb ) { fRequest.UseNewBase( newBase, newBase.Text(), "path", "//" ); } else if( !isRoot ) { fRequest.UseNewBase( newBase, NULL, "path", "//" ); ++nb; } // // Output the root url if( nb ) OutputHREF( NULL, newBase.Text(), AC_PATHBROWSER, NULL, "//", suffix.Text(), cls.Text() ); else OutputHREF( NULL, NULL, AC_PATHBROWSER, NULL, "//", suffix.Text(), cls.Text() ); if( isRoot ) return 1; // // Save length of the "last link" in path so that we can // use the relative url instead of a full path url if they are // are the same path. if( isFileBrowser ) { char *e = p4wI18n::safeStrrchr( fullPath, '/' ); llen = e - fullPath + 1; } else { llen = strlen( fullPath ); } // // If we were in workspace mode or had a pattern that needs to be reset, // we will always need a new base and cannot use a relative url. // Also, if the path to linkify is not the current path, we cannot // use a relative url. if( fRequest.GetViewMode() == VM_WORKSPACE || strcmp( fullPath, fRequest.GetPath().Text() ) ) ++needsNewBase; // // Output each remaining directory as a link, skipping the root. if( *p == '/' ) ++p; len = strlen(p); suffix.Clear(); int ru = 0; for( curDir = p; ( curDir < ( p + len ) ) && ( ( n = strchr( curDir, '/' ) ) != NULL ); curDir = n + 1 ) { // Output this entry. Skip the slash of the // first entry only. This is an artifact of using // for a link. if( skipFirstSlash ) { skipFirstSlash = 0; } else { fRequest << slash; } partialPath.Set( fullPath, n - fullPath + 1 ); // // The last link can use a relative url if we're in the path // browser, but not in workspace mode. if( !isFileBrowser && ( !needsNewBase ) && !strncmp( partialPath.Text(), fullPath, llen ) ) { *n = '\0'; OutputHREF( NULL, NULL, AC_PATHBROWSER, NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), suffix.Text(), cls.Text() ); } else { fRequest.UseNewBase( newBase, newBase.Text(), "path", partialPath.Text() ); *n = '\0'; OutputHREF( NULL, newBase.Text(), AC_PATHBROWSER, NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), suffix.Text(), cls.Text() ); } *n = '/'; } // Output the last entry. If we are in file browser mode, this is the // filename. If we are in path browser mode, this // is a directory. Both are now links. if( isFileBrowser ) { if( curDir != p ) fRequest << slash; if( !needsNewBase ) { OutputHREF( NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), AC_BROWSEFILE, NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), NULL, cls.Text() ); } else { newBase << curDir; OutputHREF( NULL, newBase.Text(), AC_BROWSEFILE, NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), NULL, cls.Text() ); } } else { if( curDir < ( p + len ) ) { if( !skipFirstSlash ) fRequest << slash; newBase.Clear(); suffix.Set( slashDots.Text() ); if( !needsNewBase ) { OutputHREF( NULL, NULL, AC_PATHBROWSER, NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), suffix.Text(), cls.Text() ); } else { OutputHREF( NULL, newBase.Text(), AC_PATHBROWSER, NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), suffix.Text(), cls.Text() ); } } else { fRequest << slashDots; } } // End this if( useBigText ) { htm.endFace( "b" ); fRequest << htm; } return 1; } int p4wPane::OutputDirHeaderWS( char *fullPath, int isFileBrowser, int useBigText ) { // // Generate a file or path description in which each subdirectory is // a link to the path browser. This assumes we are in workspace mode // and thus using local file syntax. PathSys *pathDir = PathSys::Create(); StrBuf node; StrBuf curDir; StrBuf fullP; StrBuf subDirChain; StrBuf root; StrBuf newBase; p4wHtml suffix; p4wHtml htm; p4wHtml cls; int nLevels = 0; int rootFound = 0; int nb = 0; int putback = 0; int exists = 1; const StrPtr *wr = fRequest.GetStateArg( "wr" ); const StrPtr *pattern = fRequest.GetStateArg( "pat" ); if( useBigText ) { cls.Set( "bigger" ); htm.face( "b" ); fRequest << htm; htm.Clear(); } else { cls.Set( "status" ); } // // Generate the root, counting directory levels left to process. // If workspace root is unset, or not found in this path, don't // generate links and issue an error. StrBuf wn; StrBuf fn; p4wURL urlMaker; pathDir->Set( fullPath ); if( wr ) { // // Take off the trailing delimiter for making these // comparisions UNLESS path is the root directory. if( urlMaker.IsRootDir( wr->Text() ) ) { wn.Set( wr ); } else { wn.Set( wr->Text(), wr->Length() - 1); ++putback; } if( urlMaker.IsRootDir( (const char *)fullPath ) || strlen(fullPath) < 1 ) fn.Set( fullPath ); else fn.Set( fullPath, strlen(fullPath) - 1 ); if( isFileBrowser || strcmp( fn.Text(), wn.Text() ) ) { while( pathDir->ToParent( &node ) ) { if( pathDir->Length() ) { curDir.Set( pathDir->Text() ); ++nLevels; } if( !strcmp( pathDir->Text(), wn.Text() ) ) { ++rootFound; break; } } } else { ++rootFound; curDir.Set( wr ); putback = 0; } } fWRootFound = rootFound; // // Check if file or path exists, and output an error if we are // sure that it does not exist FileSys *f = FileSys::Create( FST_TEXT ); StrBuf fp; const StrPtr *server = fRequest.GetProtocol( "server2" ); if( !isFileBrowser ) { int i = 1; #ifdef OS_NT if (strlen(fullPath) == 3 && strcmp(fullPath+1, ":\\") == 0) i = 0; #else if (strcmp(fullPath, "/") == 0) i = 0; #endif fp.Set( fullPath, strlen(fullPath) - i ); } else { if( server && server->Atoi() > 17 ) fp.Set( p4wStrBuf().UnescapeP4Chars( StrRef(fullPath) ) ); else fp.Set( fullPath ); } f->Set( fp ); if( !isFileBrowser && !( f->Stat() & FSF_DIRECTORY ) ) { exists = 0; } else if( isFileBrowser && !( f->Stat() & FSF_EXISTS ) ) { exists = 0; } delete f; if( !rootFound || !exists ) { fRequest << p4wStrBuf().EscapeHTML( StrRef(fullPath) ).Text(); if( useBigText ) { htm.endFont(); htm.endFace( "b" ); } htm.linebreak(); fRequest << htm; // // If this is the title for the Depot tree or File // browser page, suppress the error because it will // be printed later. if( fRequest.GetCmd() != AC_PATHBROWSER && fRequest.GetCmd() == AC_FILESTATE ) { if( !exists ) p4wPane::RenderError( "The path entered could not be found.", 1 ); else p4wPane::RenderError( "Workspace root is unset or not in current path!", 1 ); } delete pathDir; return 0; } // // Generate the root link root << curDir; if( nLevels < 1 ) { suffix.beginSpan( cls.Text() ); suffix << "..."; suffix.endSpan(); if( useBigText ) suffix.endFace( "b" ); } // // Put back the trailing path delimiter on the root // if we removed it. if( putback ) { root << urlMaker.GetSysDirDelim( NULL ); curDir << urlMaker.GetSysDirDelim( NULL ); } // // Unset cdf if we are in file browser if( isFileBrowser ) { fRequest.UseNewBase( newBase, NULL, "cdf", NULL ); ++nb; } // // We can't use a relative url because BASE // directive does not work in workspace mode. if( nb ) fRequest.UseNewBase( newBase, newBase.Text(), "path", root.Text() ); else fRequest.UseNewBase( newBase, NULL, "path", root.Text() ); // // Generate the root url if( nLevels < 1 ) { OutputHREF( NULL, newBase.Text(), AC_PATHBROWSER, NULL, p4wStrBuf().EscapeHTML( *wr, Unicode() ).Text(), suffix.Text(), cls.Text() ); delete pathDir; return 1; } else { OutputHREF( NULL, newBase.Text(), AC_PATHBROWSER, NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), NULL, cls.Text() ); } // // Generate the intermediate directory links char *start = fullPath + root.Length(); pathDir->Set( start ); --nLevels; int nDeep = nLevels; suffix.Clear(); suffix.beginSpan( cls.Text() ); suffix << urlMaker.GetSysDirDelim( NULL ); suffix.endSpan(); for( int nl = 0; nl < nLevels; nl++ ) { for( int nd = 0; nd < nDeep; nd++ ) pathDir->ToParent( &node ); curDir.Set( pathDir->Text() ); fullP.Set( root ); fullP << subDirChain << curDir; fullP << urlMaker.GetSysDirDelim( NULL ); subDirChain << curDir << urlMaker.GetSysDirDelim( NULL ); pathDir->Set( start + subDirChain.Length() ); --nDeep; fRequest.UseNewBase( newBase, newBase.Text(), "path", fullP.Text() ); OutputHREF( NULL, newBase.Text(), AC_PATHBROWSER, NULL, p4wStrBuf().EscapeHTML( StrRef(curDir), Unicode() ).Text(), suffix.Text(), cls.Text() ); } // // Since the path to linkify is not necessarily the // current path, use that path rather than the current // path for the last link. fRequest.UseNewBase( newBase, newBase.Text(), "path", fullPath ); // Output the last entry. If we are in file browser mode, this is the // filename. If we are in path browser mode, this // is a directory. Both are now links if( isFileBrowser ) { OutputHREF( NULL, newBase.Text(), AC_BROWSEFILE, NULL, p4wStrBuf().EscapeHTML( node ).Text(), NULL, cls.Text() ); } else { node.Set( node.Text(), node.Length() - 1 ); suffix.Clear(); suffix.beginSpan( cls.Text() ); suffix << urlMaker.GetSysDirDelim( NULL ); suffix << "..."; suffix.endSpan(); OutputHREF( NULL, newBase.Text(), AC_PATHBROWSER, NULL, p4wStrBuf().EscapeHTML( node ).Text(), suffix.Text(), cls.Text() ); } delete pathDir; // End this if( useBigText ) { htm.endFace( "b" ); fRequest << htm; } return 1; } void p4wPane::OutputIcon( const char *iconName, int height, int width, const char *alt, int suppressBorder ) { // // Output the named icon using specified characteristics StrBuf icon; p4wURL urlMaker; urlMaker.ConstructIcon( icon, iconName, height, width, alt, suppressBorder ); fRequest << icon; } void p4wPane::OutputHREF( const char *prefix, const char *path, AllCommands cmd, StrBufDict *cmdArgs, const char *link, const char *suffix, const char *cls, const char *title, const char *id, const char *jshandler, const char *jsaction ) { // // Output and URL path constructed from passed in components p4wHtml htm( 1 ); if( prefix ) fRequest << prefix; StrBuf href; fRequest.ConstructSafeURL( href, path, cmd, cmdArgs ); htm.beginLink( href.Text(), NULL, cls, 0, title, id, jshandler, jsaction ); if( link ) htm << link; htm.endLink(); fRequest << htm; if( suffix ) fRequest << suffix; } int p4wPane::PageLimitExceeded() { // // Return true if page content limit in bytes has been exceeded. Send a // warning message the first time it has been detected in this pane. if( fLimitExceeded ) return 1; if( fRequest.PageLimitExceeded() ) { p4wHtml errMsg(1); StrBuf configURL; StrBuf newPath; p4wURL urlMaker; errMsg.Set( "WARNING: data on this page has been truncated " ); errMsg << "because the page content limit was exceeded!"; errMsg.paragraph(); fRequest.UseNewBase( newPath, NULL, "path", "/" ); urlMaker.ConstructURL( configURL, newPath.Text(), AC_CONFIGURATION, NULL, fRequest.GetUnicode() ); configURL << "#pcl"; errMsg.beginLink( configURL.Text() ); errMsg.text( "Change the page content limit" ); errMsg.endLink(); RenderError( errMsg.Text(), 0 ); fLimitExceeded = 1; return 1; } return 0; } void p4wPane::ReorderSpec( StrPtr *oldSpec ) { // // Reorder the spec fields using the server's suggested // 'seq' order int nFields = 0; int fieldOrder[50]; StrBuf newSpec; // // This is currently only done for spec forms if( !fIsForm ) return; // // No server seq fields were found so do nothing if( !strstr( oldSpec->Text(), "seq" ) ) return; // // Parse the spec string to get the suggested // order and save in an array const char *end; const char *beg = oldSpec->Text(); const char *t; int fp; while( *beg ) { end = strstr( beg, ";;" ); if( !( t = strstr( beg, "seq" ) ) ) { fieldOrder[nFields++] = 0; } else if( t < end ) { t = strchr( t, ':' ) + 1; fp = atoi( t ); fieldOrder[nFields++] = fp; } else { fieldOrder[nFields++] = 0; } beg = end + 2; } // // Use fieldOrder array to reorder spec fields. // When the field position isn't found in the array // use the next field which did not have a field position // value. int pos; int i; int j; for( i = 1; i < nFields + 1; i++ ) { for( j = 0, pos = 0; j < nFields; j++ ) { if( fieldOrder[j] == i ) { pos = j + 1; break; } } if( !pos ) { for( j = 0; j < nFields; j++ ) { if( fieldOrder[j] == 0 ) { pos = j + 1; fieldOrder[j] = i; break; } } } // // The spec string sequence didn't make sense, // so don't change the order if( !pos ) return; // // Append the next field to our new specstring // in its correct position beg = oldSpec->Text(); for( j = 0; j < pos - 1; j++ ) { beg = strstr( beg, ";;" ) + 2; } end = strstr( beg, ";;" ) + 2; newSpec.Append( beg, end - beg ); } // // Replace the old spec with our newly reordered spec Error e; delete fSpec; fSpec = new Spec; fSpec->Decode( (StrPtr *)&newSpec, &e ); } void p4wPane::promptPassword( const char *err, int needsCreate ) { // // Output the specific password error, with a link // to the password help page, and also a link to either // login again, or to create a new password StrBufDict args; StrBuf actionURL; StrBuf actionPath; p4wURL urlMaker; // // Generate link to change password if requested, or if user // entered wrong password when trying to change password. if( needsCreate || fRequest.GetCmd() == AC_CONFIGPROCESSOR ) { fRequest.UseNewBase( actionPath, NULL, "path", "//" ); urlMaker.ConstructURL( actionURL, actionPath.Text(), AC_CONFIGURATION, NULL, fRequest.GetUnicode() ); ++needsCreate; // // 'pw' arg in url tells p4web to re-prompt for password } else { args.SetVar( "pw", "" ); fRequest.ConstructSafeURL( actionURL, fRequest.GetURL().Text(), fRequest.GetCmd(), &args ); } // // link to help StrBuf helpURL; StrBuf helpPath; fRequest.UseNewBase( helpPath, NULL, "path", "/auth" ); urlMaker.ConstructURL( helpURL, helpPath.Text(), AC_HELP, NULL, fRequest.GetUnicode() ); // // Output error with link to help and // link to authorization dialog or Settings page. // However, if we are in non-authenticated viewer mode, // just render an error with a link to help since the user // can do nothing until the p4web is restarted with a valid // ticket. p4wHtml newErr; if( fRequest.BypassAuth() || (needsCreate && fRequest.BrowseWithAuth()) ) { newErr.Set( "P4Web " ); newErr << "has encountered a Perforce "; newErr << "password error."; newErr.linebreak(); newErr << "Please contact your "; newErr << "P4Web administrator to "; newErr << "resolve this problem."; } else if( needsCreate ) { newErr.text( err ); newErr.linebreak(); newErr.beginLink( actionURL.Text() ); newErr.text( "Change password" ); newErr.endLink(); newErr.text( " or see the " ); newErr.beginLink( helpURL.Text() ); newErr.text( "help" ); newErr.endLink(); newErr.text( " for further information." ); } else { newErr.endForm(); newErr.text( err ); newErr.text( " See the " ); newErr.beginLink( helpURL.Text() ); newErr.text( "help" ); newErr.endLink(); newErr.text( " for further information." ); newErr.paragraph(); StrBuf orgurl; orgurl << fRequest.GetURL().Text() << fRequest.GetDynURL().Text(); char *p = strstr(orgurl.Text(), "&pw"); if (p) { orgurl.SetLength(); strcpy(p, p+3); } p4wURL url; StrBuf actionURL; url.ConstructURL( actionURL, NULL, AC_USERPSWD, NULL, fRequest.GetUnicode() ); newErr.beginLogin(actionURL.Text()); newErr.endLogin(orgurl.Text()); } // // Save the error in case it occurred on the FilePane. // The FilePane defers printing errors until after the // page is rendered, so flag this condition and save the // error so that it can generate this error instead of // the original error. fPromptedPW = 1; fPasswdErrorPage.Set( newErr ); RenderError( newErr.Text(), 0 ); } void p4wPane::PromptRetry( const char *err ) { // // When an invalid path has been supplied to the Tree or File // browsing pages, issue a prompt to re-issue the path with // links to go to the Depot Tree or Workspace Tree pages StrBuf wbase; StrBuf dbase; StrBuf depotPath; StrBuf workspacePath; p4wURL urlMaker; p4wHtml htm; StrBuf errMsg; const StrPtr *cdf = fRequest.GetStateArg( "cdf" ); char *dfrom = NULL; char *wfrom = NULL; // // Construct urls to switch to depot/workspace mode if( cdf ) { fRequest.UseNewBase( wbase, NULL, "cdf", NULL ); wfrom = wbase.Text(); fRequest.UseNewBase( dbase, NULL, "cdf", NULL ); dfrom = dbase.Text(); } if( fRequest.GetViewMode() == VM_WORKSPACE ) { fRequest.UseNewBase( dbase, dfrom, "md", "c" ); fRequest.UseNewBase( dbase, dbase.Text(), "cd", "//" ); fRequest.UseNewBase( dbase, dbase.Text(), "wr", NULL ); fRequest.UseNewBase( dbase, dbase.Text(), "path", "/" ); fRequest.UseNewBase( wbase, wfrom, "path", "/" ); } else { fRequest.UseNewBase( dbase, dfrom, "cd", "//" ); fRequest.UseNewBase( dbase, dbase.Text(), "path", "//" ); fRequest.UseNewBase( wbase, wfrom, "path", "//" ); } urlMaker.ConstructURL( workspacePath, wbase.Text(), AC_WORKSPACE, NULL, fRequest.GetUnicode() ); urlMaker.ConstructURL( depotPath, dbase.Text(), AC_PATHBROWSER, NULL, fRequest.GetUnicode() ); errMsg.Set( err ); errMsg << " Please re-enter path or choose one of the following:"; htm.beginTable(); htm.beginTRow(); htm.beginCol( NULL, NULL, "2" ); htm.text( errMsg.Text() ); htm.endCol(); htm.endTRow(); htm.beginTRow(); htm.beginCol(); htm.endCol(); htm.beginCol(); htm.beginLink( depotPath.Text() ); htm.text( "Depot Tree" ); htm.endLink(); htm.endCol(); htm.endTRow(); htm.beginCol(); htm.endCol(); htm.beginCol(); htm.beginLink( workspacePath.Text() ); if (!fRequest.isLocalRequest()) htm << "Remote "; htm.text( "Workspace Tree" ); htm.endLink(); htm.endCol(); htm.endTRow(); htm.endTable(); RenderError( htm.Text(), 0 ); } void p4wPane::doUnderline() { // // Generates a line used to underline the previous row StrBuf grayIcon; p4wURL urlMaker; p4wHtml htm; urlMaker.ConstructURL( grayIcon, "/grayPixelIcon", AC_ICON, NULL ); htm.beginTRow(); htm.beginCol( "top", NULL, "12", NULL, NULL, NULL, "5", 0, 0, "padding:0 0" ); htm.icon( grayIcon.Text(), "1", "100%", "", 1, "0", "0" ); htm.endCol(); htm.endTRow(); fRequest << htm; } void p4wPane::Message( Error *err ) { if( err->IsInfo() ) { if( err->CheckId( MsgServer::TriggerOutput ) ) { StrBuf buf; err->Fmt( buf, EF_NEWLINE ); if (!fRequest.bQuiet()) printf("%s\n", buf.Text() ); err->Clear(); return; } // Info StrBuf buf; err->Fmt( buf, EF_PLAIN ); OutputInfo( (char)err->GetGeneric() + '0', buf.Text() ); } else { #if 0 // Below is the correct way to deal with a trigger failure, but it is less user friendly // than allowing the trigger mesaage to be displayed on screen as an error message. // Therefore this code below is turned off until such time as we come up with a better // way for P4Web to handle form out trigger messages. // // Trigger output? if ( err->CheckId( MsgServer::TriggerFailed ) || err->CheckId( MsgServer::TriggersFailed ) ) { StrBuf buf; err->Fmt( buf, EF_NEWLINE ); printf("%s\n", buf.Text() ); err->Clear(); return; } #endif // warn, failed, fatal HandleError( err ); } } int p4wPane::GetFileMenuShowVals(StrDict * varList) { int show = FM_BROWSEFILE; // "Revision history" is always an option if (!varList) { const StrPtr *pb = fRequest.GetStateArg( "pb" ); if (pb) { switch(atoi(pb->Text())) { case 56: // Unsynced show += FM_SYNCCMD; // "Sync to head rev" show += FM_SYNCFILEFRM; // "Sync..." show += FM_DIFFWVH; // "Diff - head rev vs. workspace file" show += FM_WORKSPACEFILEPATH; // "Open workspace file in browser" show += FM_MIMECONTENT; // "Open head rev in browser" break; case 58: // Files missing from workspace show += FM_SYNCFILEFRM; // "Sync..." show += FM_MIMECONTENT; // "Open head rev in browser" break; case 59: // Opened unchanged files show += FM_REVERTCONFIRM; // "Revert" & "Revert unchanged" show += FM_WORKSPACEFILEPATH; // "Open workspace file in browser" show += FM_MIMECONTENT; // "Open head rev in browser" break; case 60: // Changed unopened files show += FM_SYNCFILEFRM; // "Sync..." show += FM_EDITFILE; // "Open for edit in Default" & "Open for edit..." & "Open for delete..." show += FM_DIFFWVH; // "Diff - head rev vs. workspace file" show += FM_WORKSPACEFILEPATH; // "Open workspace file in browser" show += FM_MIMECONTENT; // "Open head rev in browser" break; } } return show; } StrPtr *headType = varList->GetVar( "headType" ); StrPtr *headAction = varList->GetVar( "headAction" ); StrPtr *headRev = varList->GetVar( "headRev" ); StrPtr *haveRev = varList->GetVar( "haveRev" ); StrPtr *type = varList->GetVar( "type" ); StrPtr *action = varList->GetVar( "action" ); StrPtr *unresolved = varList->GetVar( "unresolved" ); StrPtr *ourLock = varList->GetVar( "ourLock" ); StrPtr *otherLock = varList->GetVar( "otherLock" ); StrPtr *otherOpen = varList->GetVar( "otherOpen" ); StrPtr *curFile = fRequest.GetViewMode() == VM_WORKSPACE ? varList->GetVar( "clientFile" ) : varList->GetVar( "depotFile" ); char *filename = curFile->Text() + fRequest.GetPath().Length(); int plusl = 0; int isText = 0; // determine if text or not char *t = type ? type->Text() : (headType ? headType->Text() : NULL); if (!t || strstr(t, "text") || strstr(t, "unicode") || strstr(t, "symlink") || strstr(t, "utf16")) isText = 1; // if filetype includes +1, set the correct lock flag if (headType) { char * offset = strchr(headType->Text(), '+'); if (offset) { if (strchr(offset, 'l')) { if (action) ourLock = (StrPtr *)1; else if (otherOpen) otherLock = (StrPtr *)1; plusl = 1; } } } if (headRev && (!haveRev || (*haveRev != *headRev))) show += FM_SYNCCMD; // "Sync to head rev" if (headRev) show += FM_SYNCFILEFRM; // "Sync..." if (haveRev) show += FM_REMOVEFILEFRM; // "Remove from workspace" if (!action) { if (headType) show += FM_EDITFILE; // "Open for edit in Default" & "Open for edit..." & "Open for delete..." else show += FM_ADDFILEFRM; // "Open for add..." } else { show += FM_REVERTCONFIRM; // "Revert" & "Revert unchanged" } if (isText) { if (headRev) show += FM_FILETEXTDEPOT; // "View - head rev text" & "View - annotated file text" & "View - fully annotated file text" show += FM_FILETEXTLOCAL; // "View - workspace file text" } if (headRev) show += FM_MIMECONTENT; // "Open head rev in browser" show += FM_WORKSPACEFILEPATH; // "Open workspace file in browser" if (action && (*action == "edit" || *action == "add")) { if (isText) { if (fRequest.isLocalRequest()) show += FM_LAUNCHEDITOR; // "Open workspace file in editor" show += FM_EDITTEXTLOCAL; // "Edit workspace file in browser" } show += FM_UPLOADTOLOCAL; // "Upload file to workspace" } if (haveRev && headRev && *haveRev != *headRev) show += FM_DIFF_WVC_CVH; // "Diff - synced rev vs. workspace file" & "Diff - synced rev vs. head rev" if (headRev && *headRev > "1") show += FM_DIFF21; // "Diff - prev rev vs. head rev" if (headRev) show += FM_DIFFWVH; // "Diff - head rev vs. workspace file" if (action && (*action == "edit" || *action == "add")) show += FM_FILETYPEFILEFRM; // "Change filetype..." if (headType && (!action || *action != "add")) show += FM_INTEGRATEFILEFRM; // "Integrate..." if (!plusl && action) { if (ourLock) show += FM_UNLOCKFILEFRM; // "Unlock" else if (!otherLock) show += FM_LOCKFILEFRM; // "Lock" } if (unresolved) show += FM_RESOLVEFILEFRM; // "Resolve..." return show; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 12234 | Matt Attaway |
Rejigger P4Web project in preparation for official sunsetting The bin directory contains the last official builds of P4Web from the Perforce download site. P4Web is soon to be completely sunsetted; these builds are here for folks who don't want to build their own. To better handle the archived builds the source code has been moved into a separate src directory. |
||
//guest/perforce_software/p4web/Panes/p4wPane.cpp | |||||
#1 | 8914 | Matt Attaway | Initial add of the P4Web source code |