Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
104
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

478 C H A P T E R 1 3 F I L E S A N D S T R E A M S

Table 13-6. Continued

Member

Description

Name

Returns the drive letter name of the drive (such as C: or E:).

VolumeLabel

Returns the descriptive volume label for the drive. In an NTFS-formatted

 

drive, the volume label can be up to 32 characters. If not set, this property

 

returns null.

RootDirectory

Returns a DirectoryInfo object for the root directory in this drive.

GetDrives()

Retrieves an array of DriveInfo objects, representing all the logical drives

 

on the current computer.

 

 

Tip Attempting to read from a drive that’s not ready (for example, a CD drive that doesn’t currently have a CD in it) will throw an exception. To avoid this problem, check the DriveInfo.IsReady property and attempt to read other properties only if the DriveInfo.IsReady property returns true.

Working with Attributes

The Attributes property of the FileInfo and DirectoryInfo classes represent the file system attributes for the file or directory. Because every file and directory can have a combination of attributes, the Attributes property contains a combination of values from the FileAttributes enumeration. Table 13-7 describes these values.

Table 13-7. Values for the FileAttributes Enumeration

Value

Description

Archive

The item is archived. Applications can use this attribute to mark files

 

for backup or removal, although it’s really just a holdover from older

 

DOS-based operating systems.

Compressed

The item is compressed.

Device

Not currently used. Reserved for future use.

Directory

The item is a directory.

Encrypted

This item is encrypted. For a file, this means that all data in the file is

 

encrypted. For a directory, this means that encryption is the default for

 

newly created files and directories.

Hidden

This item is hidden and thus is not included in an ordinary directory

 

listing. However, you can still see it in Windows Explorer.

Normal

This item is normal and has no other attributes set. This attribute is

 

valid only if used alone.

NotContentIndexed

This item will not be indexed by the operating system’s content

 

indexing service.

Offline

This file is offline and not currently available.

ReadOnly

This item is read-only.

ReparsePoint

This file contains a reparse point, which is a block of user-defined data

 

associated with a file or a directory in an NTFS file system.

C H A P T E R 1 3 F I L E S A N D S T R E A M S

479

Value

Description

SparseFile

The file is a sparse file. Sparse files are typically large files with data

 

consisting of mostly zeros. This item is supported only on NTFS file

 

systems.

System

The item is part of the operating system or is used exclusively by the

 

operating system.

Temporary

This item is temporary and can be deleted when the application is no

 

longer using it.

 

 

To find out all the attributes a file has, you can enumerate over the values in the Attributes property, as shown here:

foreach (FileAttributes attribute in myFile.Attributes) { ... }

You can also call the ToString() method of the Attributes property. This returns a string with a comma-separated list of attributes:

// This displays a string in the format "ReadOnly, Archive, Encrypted" lblInfo.Text = myFile.Attributes.ToString();

When testing for a single specific attribute, you need to use bitwise arithmetic. For example, consider the following faulty code:

if (myFile.Attributes == FileAttributes.ReadOnly) { ... }

This test succeeds only if the read-only attribute is the only attribute for the current file. This is rarely the case. If you want to successfully check whether the file is read-only, you need this code instead:

if ((myFile.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { ... }

This test succeeds because it filters out just the read-only attribute. Essentially, the Attributes setting consists (in binary) of a series of ones and zeros, such as 00010011. Each 1 represents an attribute that is present, and each 0 represents an attribute that is not. When you use the & operator with an enumerated value, it automatically performs a bitwise and operation, which compares each digit against each digit in the enumerated value. For example, if you combine a value of 00100001 (representing an individual file’s archive and read-only attributes) with the enumerated value 00000001 (which represents the read-only flag), the resulting value will be 00000001. It will have a

1 only where it can be matched in both values. You can then test this resulting value against the FileAttributes.ReadOnly enumerated value using the equal sign.

Similar logic allows you to verify that a file does not have a specific attribute:

if ((myFile.Attributes & FileAttributes.ReadOnly) =! FileAttributes.ReadOnly) { ... }

When setting an attribute, you must also use bitwise arithmetic. In this case, it’s needed to ensure that you don’t inadvertently wipe out the other attributes that are already set.

// This adds just the read-only attribute.

myFile.Attributes = myFile.Attributes | FileAttributes.ReadOnly;

// This removes just the read-only attribute.

myFile.Attributes = myFile.Attributes & ~FileAttributes.ReadOnly;

480 C H A P T E R 1 3 F I L E S A N D S T R E A M S

Note Some attributes can’t be set programmatically. For example, the Encrypted attributed is set by the operating system only if you are using EFS (Encrypting File System) to encrypt files.

Filter Files with Wildcards

The DirectoryInfo and Directory objects both provide a way to search the current directories for files or directories that match a specific filter expression. These search expressions can use the standard ? and * wildcards. The ? wildcard represents any single character, and the * wildcard represents any sequence of zero or more characters.

For example, the following code snippet retrieves the names of all the files in the c:\temp directory that have the extension .txt. The code then iterates through the retrieved FileInfo collection of matching files and displays the name and size of each one.

DirectoryInfo dir = new DirectoryInfo(@"c:\temp");

//Get all the files with the .txt extension. FileInfo[] files = dir.GetFiles("*.txt");

//Process each file.

foreach (FileInfo file in files) { ... }

You can use a similar technique to retrieve directories that match a specified search pattern by using the overloaded DirectoryInfo.GetDirectories() method.

The GetFiles() and GetDirectories() methods search only the current directory. If you want to perform a search through all the contained subdirectories, you’d need to use recursive logic.

Retrieving File Version Information

File version information is the information you see when you look at the properties of an EXE or DLL file in Windows Explorer. Version information commonly includes a version number, the company that produced the component, trademark information, and so on.

The FileInfo and File classes don’t provide a way to retrieve file version information. However, you can retrieve it quite easily using the static GetVersionInfo() method of the System.Diagnostics.FileVersionInfo class. The following example uses this technique to get a string with the complete version information and then displays it in a label:

string fileName = @"c:\Windows\explorer.exe";

FileVersionInfo info = FileVersionInfo.GetVersionInfo(fileName); lblInfo.Text = info.FileVersion;

Table 13-8 lists the properties you can read.

Table 13-8. FileVersionInfo Properties

Property

Description

FileVersion, FileMajorPart,

Typically, a version number is displayed as

FileMinorPart, FileBuildPart,

[MajorNumber].[MinorNumber].[BuildNumber].[Private

and FilePrivatePart

PartNumber]. These properties allow you to retrieve the

 

complete version as a string (FileVersion) or each individual

 

component as a number.

FileName

Gets the name of the file that this instance of FileVersionInfo

 

describes.

C H A P T E R 1 3 F I L E S A N D S T R E A M S

481

Property

Description

OriginalFilename

Gets the name the file was created with.

InternalName

Gets the internal name of the file, if one exists.

FileDescription

Gets the description of the file.

CompanyName

Gets the name of the company that produced the file.

ProductName

Gets the name of the product this file is distributed with.

ProductVersion,

These properties allow you to retrieve the complete product

ProductMajorPart,

version as a string (ProductVersion) or each individual

ProductMinorPart,

component as a number.

ProductBuildPart,

 

and ProductPrivatePart

 

IsDebug

Gets a Boolean value that specifies whether the file contains

 

debugging information or is compiled with debugging features

 

enabled.

IsPatched

Gets a Boolean value that specifies whether the file has been

 

modified and is not identical to the original shipping file of the

 

same version number.

IsPreRelease

Gets a Boolean value that specifies whether the file is a

 

development version, rather than a commercially released

 

product.

IsPrivateBuild

Gets a Boolean value that specifies whether the file was built

 

using standard release procedures.

IsSpecialBuild

Gets a Boolean value that specifies whether the file is a special

 

build.

SpecialBuild

If IsSpecialBuild is true, this property contains a string that

 

specifies how the build differs from an ordinary build.

Comments

Gets the comments associated with the file.

Language

Gets the default language string for the version info block.

LegalCopyright

Gets all copyright notices that apply to the specified file.

LegalTrademarks

Gets the trademarks and registered trademarks that apply to

 

the file.

 

 

The Path Class

If you’re working with files, you’re probably also working with file and directory paths. Path information is stored as an ordinary string, which can lead to a number of problems ranging from minor headaches to serious security breaches.

For example, imagine you write the following block of code to add a filename to a path:

DirectoryInfo dirInfo = new DirectoryInfo(@"c:\temp\"); string file = "test.txt";

string path = dirInfo.FullName + @"\" + file;

At first, this code appears to work correctly. However, a problem occurs with the last line if you try to process the root directory. Here’s an example of the error:

DirectoryInfo dirInfo = new DirectoryInfo(@"c:\"); string file = "test.txt";

string path = dirInfo.FullName + @"\" + file;

482 C H A P T E R 1 3 F I L E S A N D S T R E A M S

The problem here is that the FullName property never returns a trailing backslash. For example, c:\temp\ becomes just c:\temp. However, there’s one exception—the root directory c:\, which always includes a trailing backslash. As a result, this seemingly logical code generates the nonsensical path c:\\test.txt and fails.

The proper solution is to use the System.IO.Path class, which provides static helper methods that perform common path manipulation tasks. In this case, the Combine() method neatly solves the problem and works with any directory and file, as follows:

DirectoryInfo dirInfo = new DirectoryInfo(@"c:\"); string file = "test.txt";

string path = Path.Combine(dirInfo.FullName, file);

Minor hiccups like this are bothersome, but they aren’t serious. A more significant problem is the security risk of a canonicalization error. Canonicalization errors are a specific type of application error that can occur when your code assumes that user-supplied values will always be in a standardized form. Canonicalization errors are low-tech but quite serious, and they usually have the result of allowing a user to perform an action that should be restricted.

One infamous type of canonicalization error is SQL injection, whereby a user submits incorrectly formatted values to trick your application into executing a modified SQL command. (Chapter 7 covered SQL injection in detail). Other forms of canonicalization problems can occur with file paths and URLs.

For example, consider the following method that returns file data from a fixed document direc-

tory:

FileInfo file = new FileInfo(Server.MapPath("Documents\\" + txtBox.Text)); // (Read the file and display it in another control).

This code looks simple enough. It concatenates the user-supplied filename with the Documents path, thereby allowing the user to retrieve data from any file in this directory. The problem is that filenames can be represented in multiple formats. Instead of submitting a valid filename, an attacker can submit a qualified filename such as ..\filename. The concatenated path of WebApp\Documents\..\filename will actually retrieve a file from the parent of the Documents directory (WebApp). A similar approach will allow the user to specify any filename on the web application drive. Because the web service is limited only according to the restrictions of the ASP.NET worker process, the user may be allowed to download a sensitive server-side file.

The fix for this code is fairly easy. Once again, you can use the Path class. This time, you use the GetFileName() method to extract just the final filename portion of the string, as shown here:

fileName = Path.GetFileName(fileName); FileInfo file = new FileInfo(Server.MapPath(

Path.Combine("Documents", txtBox.Text));

This ensures that the user is constrained to the correct directory. If you are dealing with URLs, you can work similar magic with the System.Uri type. For example, here’s how you might remove query string arguments from a URI and make sure it refers to a given server and virtual directory:

string uriString = "http://www.wrongsite.com/page.aspx?cmd=run";

Uri uri = new Uri(uriString);

string page = System.IO.Path.GetFileName(uri.AbsolutePath); // page is now just "page.aspx"

Uri baseUri = new Uri("http://www.rightsite.com"); uri = new Uri(baseUri, page);

// uri now stores the path http://www.rightsite.com/page.aspx.

Table 13-9 lists the methods of the Path class.

C H A P T E R 1 3 F I L E S A N D S T R E A M S

483

Table 13-9. Path Methods

Methods

Description

Combine()

Combines a path with a filename or a subdirectory.

ChangeExtension()

Modifies the current extension of the file in a string. If no

 

extension is specified, the current extension will be

 

removed.

GetDirectoryName()

Returns all the directory information, which is the text

 

between the first and last directory separators (\).

GetFileName()

Returns just the filename portion of a path.

GetFileNameWithoutExtension()

This method is similar to GetFileName(), but it omits the

 

extension from the returned string.

GetFullPath()

This method has no effect on an absolute path, and it

 

changes a relative path into an absolute path using the

 

current directory. For example, if c:\Temp\ is the current

 

directory, calling GetFullPath() on a filename such as

 

test.txt returns c:\Temp\test.txt.

GetPathRoot()

Retrieves a string with the root (for example, C:\), provided

 

that information is in the string. For a relative path, it

 

returns a null reference.

HasExtension()

Returns true if the path ends with an extension.

IsPathRooted()

Returns true if the path is an absolute path and false if it’s a

 

relative path.

 

 

Although the Path class contains methods for drilling down the directory structure (adding subdirectories to directory paths), it doesn’t provide any methods for going back up (removing subdirectories from directory paths). However, you can work around this limitation by using the Combine() method with the relative path .., which means “move one directory up.” For good measure, you can also use the GetFullPath() method on the result to return it to a normal form.

Here’s an example:

string path = @"c:\temp\subdir";

path = Path.Combine(path, "..");

// path now contains the string "c:\temp\subdir\.."

path = Path.GetFullPath(path);

// path now contains the string "c:\temp"

Note In most cases, an exception will be thrown if you supply a path that contains illegal characters to one of these methods. However, path names that contain a wildcard character (* or ?) will not cause the methods to throw an exception.

A File Browser

Using the concepts you’ve learned so far, it’s quite straightforward to put together a simple filebrowsing application. Rather than iterating through collections of files and directories manually, this example handles everything using the GridView and some data binding code.

484 C H A P T E R 1 3 F I L E S A N D S T R E A M S

Figure 13-1 shows this program in action as it displays the contents of the c:\Documents and Settings directory.

Figure 13-1. Browsing the file system

The directory listing is built using two separate GridView controls, one on top of the other. The topmost GridView shows the directories, and the GridView underneath shows files. The only visible differences to the user are that the directories don’t display length information and have a folder icon next to their names. The ShowHeader property of the second GridView is set to false so that the two grids blend into each other fairly seamlessly. And because the GridView controls are stacked together, as the list of directories grows the list of files moves down the page to accommodate it.

Technically, you could handle the directory and file listing using one GridView object. That’s because all FileInfo and DirectoryInfo objects have a common parent—the FileSystemInfo object. However, in this grid you want to show the size in bytes of each file, and you want to differentiate the appearance (in this case, through different icons). Because the DirectoryInfo object doesn’t provide a Length property, trying to bind to it in a more generic list of FileSystemInfo objects would cause an error.

Note This problem has another, equally effective solution. You could create a single GridView but not bind directly to the FileInfo.Length property. Instead, you would bind to a method in the page class that examines the current data object and return either the length (for FileInfo objects) or a blank string (for DirectoryInfo objects). You could construct a similar method to hand out the correct icon URL.

Here’s the declaration for the GridView control that provides the file list, without the formattingspecific style properties:

C H A P T E R 1 3 F I L E S A N D S T R E A M S

485

<asp:GridView ID="gridDirList" runat="server" AutoGenerateColumns="False" OnSelectedIndexChanged="gridDirList_SelectedIndexChanged" GridLines="None" CellPadding="0" CellSpacing="1" DataKeyNames="FullName">

<Columns>

<asp:TemplateField>

<ItemTemplate>

<img src="folder.jpg" /> </ItemTemplate>

</asp:TemplateField>

<asp:ButtonField DataTextField="Name" CommandName="Select" HeaderText="Name" />

<asp:BoundField HeaderText="Size" />

<asp:BoundField DataField="LastWriteTime" HeaderText="Last Modified" /> </Columns>

</asp:GridView>

This grid binds to an array of DirectoryInfo objects and displays the Name, Size, and LastWriteTime properties. In addition, the FullName property is designated as a key field so that you can return the full path after the user clicks one of the directories. You’ll also notice that one of the columns doesn’t actually display any information—that’s the BoundColumn for length that displays header text, but it doesn’t link to any data field.

The GridView for the files follows immediately. Here’s the slightly shortened control tag:

<asp:GridView ID="gridFileList" runat="server" AutoGenerateColumns="False" OnSelectedIndexChanged="gridFileList_SelectedIndexChanged" GridLines="None" CellPadding="0" CellSpacing="1" DataKeyNames="FullName">

<SelectedRowStyle BackColor="#C0FFFF" /> <Columns>

<asp:TemplateField>

<ItemTemplate>

<img src="file.jpg" /> </ItemTemplate>

</asp:TemplateField>

<asp:ButtonField DataTextField="Name" CommandName="Select" /> <asp:BoundField DataField="Length" />

<asp:BoundField DataField="LastWriteTime" /> </Columns>

</asp:GridView>

Note that the GridView for displaying files must define a SelectedRowStyle because it supports file selection.

The next step is to write the code that fills these controls. The star of the show is a private method named ShowDirectoryContents(), which retrieves the contents of the current folder and binds the two GridView controls. Here’s the complete code:

private void ShowDirectoryContents(string path)

{

//Define the current directory. DirectoryInfo dir = new DirectoryInfo(path);

//Get the DirectoryInfo and FileInfo objects. FileInfo[] files = dir.GetFiles(); DirectoryInfo[] dirs = dir.GetDirectories();

486C H A P T E R 1 3 F I L E S A N D S T R E A M S

//Show the directory listing. lblCurrentDir.Text = "Currently showing " + path; gridFileList.DataSource = files; gridDirList.DataSource = dirs;

Page.DataBind();

//Clear any selection. gridFileList.SelectedIndex = -1;

//Keep track of the current path. ViewState["CurrentPath"] = path;

}

When the page first loads, it calls this method to show the current application directory:

protected void Page_Load(object sender, System.EventArgs e)

{

if (!Page.IsPostBack)

{

ShowDirectoryContents(Server.MapPath("."));

}

}

You’ll notice that the ShowDirectoryContents() method stores the currently displayed directory in view state. That allows the Move Up button to direct the user to a directory that’s one level above the current directory:

protected void cmdUp_Click(object sender, System.EventArgs e)

{

string path = (string)ViewState["CurrentPath"]; path = Path.Combine(path, "..");

path = Path.GetFullPath(path); ShowDirectoryContents(path);

}

To move down through the directory hierarchy, the user simply needs to click a directory link. This is raised as a SelectedIndexChanged event. The event handler then displays the new directory:

protected void gridDirList_SelectedIndexChanged(object source, EventArgs e)

{

// Get the selected directory.

string dir = (string)gridDirList.DataKeys[gridDirList.SelectedIndex].Value;

//Now refresh the directory list to

//show the selected directory. ShowDirectoryContents(dir);

}

But what happens if a user selects a file from the second GridView? In this case, the code retrieves the full file path, creates a new FileInfo object, and binds it to a FormView control, which uses a template to display several pieces of information about the file. Figure 13-2 shows the result.

C H A P T E R 1 3 F I L E S A N D S T R E A M S

487

Figure 13-2. Examining a file

Here’s the code that binds the file information when a file is selected:

protected void gridFileList_SelectedIndexChanged(object sender, System.EventArgs e)

{

// Get the selected file.

string file = (string)gridFileList.DataKeys[gridFileList.SelectedIndex].Value;

//The FormView shows a collection (or list) of items.

//To accommodate this model, you must add the file object

//to a collection of some sort.

ArrayList files = new ArrayList(); files.Add(new FileInfo(file));

// Now show the selected file. formFileDetails.DataSource = files; formFileDetails.DataBind();

}

The FormView uses the following template:

<asp:FormView id="formFileDetails" runat="server"> <ItemTemplate>

<b>File:

<%# DataBinder.Eval(Container.DataItem, "FullName") %></b><br> Created at

<%# DataBinder.Eval(Container.DataItem, "CreationTime") %><br> Last updated at

<%# DataBinder.Eval(Container.DataItem, "LastWriteTime") %><br> Last accessed at

<%# DataBinder.Eval(Container.DataItem, "LastAccessTime") %><br> <i><%# DataBinder.Eval(Container.DataItem, "Attributes") %></i><br> <%# DataBinder.Eval(Container.DataItem, "Length") %> bytes.