Create temporary link for download - asp.net

I use ASP.NET
I need to give user temporary link for downloading file from server.
It should be a temporary link (page), which is available for a short time (12 hours for example). How can I generate this link (or temporary web page with link)?

Here's a reasonably complete example.
First a function to create a short hex string using a secret salt plus an expiry time:
public static string MakeExpiryHash(DateTime expiry)
{
const string salt = "some random bytes";
byte[] bytes = Encoding.UTF8.GetBytes(salt + expiry.ToString("s"));
using (var sha = System.Security.Cryptography.SHA1.Create())
return string.Concat(sha.ComputeHash(bytes).Select(b => b.ToString("x2"))).Substring(8);
}
Then a snippet that generates a link with a one week expiry:
DateTime expires = DateTime.Now + TimeSpan.FromDays(7);
string hash = MakeExpiryHash(expires);
string link = string.Format("http://myhost/Download?exp={0}&k={1}", expires.ToString("s"), hash);
Finally the download page for sending a file if a valid link was given:
DateTime expires = DateTime.Parse(Request.Params["exp"]);
string hash = MakeExpiryHash(expires);
if (Request.Params["k"] == hash)
{
if (expires < DateTime.UtcNow)
{
// Link has expired
}
else
{
string filename = "<Path to file>";
FileInfo fi = new FileInfo(Server.MapPath(filename));
Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);
Response.AddHeader("Content-Length", fi.Length.ToString());
Response.WriteFile(fi.FullName);
Response.Flush();
}
}
else
{
// Invalid link
}
Which you should certainly wrap in some exception handling to catch mangled requests.

http://example.com/download/document.pdf?token=<token>
The <token> part is key here. If you don't want to involve a database, encrypt link creation time, convert it to URL-safe Base64 representation and give user that URL. When it's requested, decrypt token and compare date stored in there with current date and time.
Alternatively, you can have a separate DownloadTokens table wich will map said tokens (which can be GUIDs) to expiration dates.

Append a timestamp to the URL, in the querystring:
page.aspx?time=2011-06-22T22:12
Check the timestamp against the current time.
To avoid the user changing the timestamp by himself, also compute some secret hash over the timestamp, and also append that to the querystring:
page.aspx?time=2011-06-22T22:12&timehash=4503285032
As hash you can do something like the sum of all fields in the DateTime modulo some prime number, or the SHA1 sum of the string representation of the time. Now the user will not be able to change the timestamp without knowing the correct hash. In your page.aspx, you check the given hash against the hash of the timestamp.

There's a million ways to do it.
The way I did once for a project was to generate a unique key and use a dynamic downloader script to stream the file. when the file request was made the key was generated and stored in db with a creation time and file requested. you build a link to the download script and passed in the key. from there it was easy enough to keep track of expiration.

llya
I'll assume you're not requiring any authentication and security isn't an issue - that is if anyone gets the URL they will also beable to download the file.
Personally I'd create a HttpHandler and then create some unique string that you can append to the URL.
Then within the ProcessRequest void test the encoded param to see if it's still viable (with in your specified time-frame) if so use BinaryWrite to render the File or if not you can render some HTML using Response.Write("Expired")
Something like :
public class TimeHandler : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest ( HttpContext context )
{
if( this.my_check_has_expired( this.Context.Request.Params["my_token"] ) )
{
// Has Expired
context.Response.Write( "URL Has Expired" );
return;
}
// Render the File
Stream stream = new FileStream( File_Name , FileMode.Open );
/* read the bytes from the file */
byte[] aBytes = new byte[(int)oStream.Length];
stream.Read( aBytes, 0, (int)oStream.Length );
stream.Close( );
// Set Headers
context.Response.AddHeader( "Content-Length", aBytes.Length.ToString( ) );
// ContentType needs to be set also you can force Save As if you require
// Send the buffer
context.Response.BinaryWrite( aBytes );
}
}
You need to then setup the Handler in IIS, but that a bit different depending on the version you're using.

Related

Share Session State Between ASP.NET and Classic ASP

Great Article on this subject here: https://searchwindevelopment.techtarget.com/tip/Share-session-state-between-ASP-and-ASPNET-apps
Problem is, I cant get it to work without source code. The code snippets in the article show many errors in Visual Studios. The author, Dennis Hurst is unreachable, as it was written in 2004. Anybody out there have the actual Source code they can post ? Or maybe point me in the right direction ? My goal is to pull Classic ASP object data (Application) into ASP.Net code that shares the same folder. I have read that it might be possible using a COMM Wrapper, but that is way out of my skill level. This sounds like the best solution for my problem. Thank You in advance for your help.
// The constructor for this class takes a reference to the HttpContext and derives the URL it will need to send its requests to
public ASPSessionVar(HttpContext oInContext)
{
oContext = oInContext;
ASPSessionVarASP = "SessionVar.asp";
/* We now build a System.Uri object to derive the correct
URL to send the HTTP request to. oContext.Request.Url
will contain a System.Uri object that represents
this ASPXs URL.
*/
System.Uri oURL = oContext.Request.Url;
ASPSessionVarASP = oURL.Scheme + "://"
+ oURL.Host + ":" + oURL.Port.ToString()
+ ASPSessionVarASP;
}
//-------------------------------------------------------------------------//
// The primary function for this example is called GetSessionVar. It does the majority of the work done by this application,
// This includes creating a WebRequest, sending it off to the ASP page, and returning the response.
// First get the Session Cookie
string ASPCookieName = "";
string ASPCookieValue = "";
if (!GetSessionCookie
(out ASPCookieName, out ASPCookieValue))
{
return "";
}
// Initialize the WebRequest.
HttpWebRequest myRequest =
(HttpWebRequest)WebRequest.Create
(ASPSessionVarASP + "?SessionVar=" + ASPSessionVar);
myRequest.Headers.Add
("Cookie: " + ASPCookieName + "=" + ASPCookieValue);
// Send the request and get a response
HttpWebResponse myResponse =
(HttpWebResponse)myRequest.GetResponse();
Stream receiveStream = myResponse.GetResponseStream();
System.Text.Encoding encode =
System.Text.Encoding.GetEncoding("utf-8");
StreamReader readStream =
new StreamReader(receiveStream, encode);
string sResponse = readStream.ReadToEnd();
// Do a bit of cleanup
myResponse.Close();
readStream.Close();
return sResponse;
}
//------------------------------------------------------------------------------------------------------------------------//
// This function simply takes the Request that was passed by the client and extracts the ASP Session cookie from it.
// This function is called by the GetSessionVar function to retrieve the ASPSession cookie.
private bool GetSessionCookie
(out string ASPCookieName, out string ASPCookieValue)
{
int loop1;
HttpCookie myCookie; // Cookie variable
ASPCookieName = "";
ASPCookieValue = "";
// Capture all cookie names into a string array.
String[] CookieArray =
oContext.Request.Cookies.AllKeys;
// Grab individual cookie objects by cookie name.
for (loop1 = 0; loop1 < CookieArray.Length; loop1++)
{
myCookie =
oContext.Request.Cookies[CookieArray[loop1]];
if (myCookie.Name.StartsWith("ASPSESSION"))
{
ASPCookieName = myCookie.Name;
ASPCookieValue = myCookie.Value;
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------------------------------//
//The ASPX page will instantiate an ASPSessionVar object, passing in the current Context to the construct or.
//The GetSessionVar function is then called, passing in the name of the ASP Session variable that is to be retrieved.
//Create an ASPSessionVar object,
//passing in the current context
SPI.WebUtilities.ASP.ASPSessionVar oASPSessionVar
= new SPI.WebUtilities.ASP.ASPSessionVar(Context);
string sTemp = oASPSessionVar.GetSessionVar("FirstName");
// CLASSIC ASP CODE BELOW !!
//The ASP code for this example was placed in an ASP file called SessionVar.asp.
// It performs two simple tasks. First, it ensures that the request is coming from the server that the ASP page is running on.
// This ensures that the request is valid and coming ONLY from the Web server's IP address.
// The ASP page then returns the session variable it was asked to provide
<%
dim sT
if Request.ServerVariables("REMOTE_ADDR") =
Request.ServerVariables("LOCAL_ADDR") then
sT = Request("SessionVar")
if trim(sT) <> "" then
Response.Write Session(sT)
end if
end if
%>

Store JWT compressed in cookie using Java

I am using Java servlet to return cookies with JWT information to client.
I return 2 cookies.
1 => Token_id, this cookie store the id_token
2 => refresh_token, this token store the refresh token to be send when the token is expired.
The problem is, these informations are too big, so the browser send this error:
431 (Request Header Fields Too Large)
To resolve it, I would like to store the refresh_token compressed, and when I will use this information I will decompress.
I read about the compress and tried to use a GZIPCompress String, like this:
public static String compress(final String str) throws IOException {
if ((str == null) || (str.length() == 0)) {
return null;
}
ByteArrayOutputStream obj = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(obj);
gzip.write(str.getBytes("UTF-8"));
gzip.flush();
gzip.close();
return obj.toString();
}
When I add the compress String to cookie, I have this exception:
Illegal character in cookie value
I can not store the refresh_token in my server.
How can I compress a big String to store in a cookie? I need to decrease the size of value cookie.
A cookie can only store strings, but you are storing binary data.
The result of the compression process is binary and is not representable using a string, even if you call obj.toString();. You would need to encode the result to base64 to get a string that could be stored in a cookie, but base64 increases the size about 30%, so I do not know if it makes sense in your case.
//storage
cookie = base64(gzip(str))
//retrieval
str = gunzip(base64decode(cookie))
The refresh token usually consists in a random string. May be you are including too much data?

Node.js Password Encryption Same As In Asp.Net

I have created a wesite in asp.net and use ms-sql database to save the records. Now want to convert it in node.js application. And want to use same sql database. In asp.net application I have encrypt the password for registered user. Below is code.
public static string CreateHash(string unHashed)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] data = System.Text.Encoding.ASCII.GetBytes(unHashed);
data = x.ComputeHash(data);
return System.Text.Encoding.ASCII.GetString(data);
}
public static bool MatchHash(string HashData, string HashUser)
{
HashUser = CreateHash(HashUser);
if (HashUser == HashData)
return true;
else
return false;
}
Now problem is that how I use same encryption in node.js. So when node application is ready old user can also make login. It only possible if node app also use same encryption that I have use in asp.net.
For node I have created all environment and use mssql module for database communication. Please help me fix that. Thanks!!
First of all MD5 shall no longer be used if you are serious about security.
Based on your comment and code, I fear there is a 'data loss' in the initial ASP.net code.
Let us have a look at CreateHash function again, I've added comments:
public static string CreateHash(string unHashed)
{
System.Security.Cryptography.MD5CryptoServiceProvider x = new System.Security.Cryptography.MD5CryptoServiceProvider();
// Convert unHashed string to bytes using ASCII coding
byte[] data = System.Text.Encoding.ASCII.GetBytes(unHashed);
// Compute MD5 hash from bytes
data = x.ComputeHash(data);
// Decode MD5 resulting bytes as ASCII
return System.Text.Encoding.ASCII.GetString(data);
}
The last line confuses me, it is decoding bytes received from MD5 function as if they were ASCII, but that is incorrect assumption. And the resulting encoded string as you gave in comment contains lots of "?'s".
Next node.js code will do similar except encode the string using hex rather than ascii:
var crypto = require('crypto')
function createHash(data) {
return crypto.createHash('md5').update(data, 'ascii').digest('hex')
}
To emulate "bytes to ascii" you could try .digest('binary') instead of hex. If it does not give what you expect, then you have to make a separate 'conversion' step from hex to ascii. (I am not experienced enough to give you elegant solution to the later one)

How can I access session variables when the page is loaded using a SimpleWorkerRequest?

I'm reading an ASPX file as a string and using the returned HTML as the source for an email message. This is the code:
public string GetEmailHTML(int itemId)
{
string pageUrl = "HTMLEmail.aspx";
StringWriter stringWriter = new StringWriter();
HttpRuntime.ProcessRequest(new SimpleWorkerRequest(pageUrl, "ItemId=" + itemId.ToString(), stringWriter));
stringWriter.Flush();
stringWriter.Close();
return stringWriter.ToString();
}
HTMLEmail.aspx uses the ItemId query string variable to load data from a DB and populate the page with results. I need to secure the HTMLEmail.aspx page so a manipulated query string isn't going to allow just anybody to see the results.
I store the current user like this:
public User AuthenticatedUser
{
get { return Session["User"] as User; }
set { Session["User"] = value; }
}
Because the page request isn't made directly by the browser, but rather the SimpleWorkerRequest, there is no posted SessionId and therefore HTMLEmail.aspx cannot access any session variables. At least, I think that's the problem.
I've read the overview on session variables here: http://msdn.microsoft.com/en-us/library/ms178581.aspx
I'm wondering if I need to implement a custom session identifier. I can get the current SessionId inside the GetEmailHTML method and pass it as a query string param into HTMLEmail.aspx. If I have the SessionId inside HTMLEmail.aspx I could maybe use the custom session identifier to get access to the session variables.
That fix sounds messy. It also removes the encryption layer ASP automatically applies to the SessionId.
Anyone have a better idea?
As far as I can see, your best bet is to pass on all the values you need inside HTMLEmail.aspx to it via the query parameters, just like you do with ItemId.
Apart from that, you can probably get away with just sending in the UserId of the user to that page and make it hit the DB (or wherever you are storing your users) to the User object, instead of trying to read it off the Session variables.
Edit:
Why don't you use:
public string GetEmailHTML(int itemId)
{
string pageUrl = "HTMLEmail.aspx";
StringWriter stringWriter = new StringWriter();
Server.Execute(pageUrl, stringWriter);
stringWriter.Flush();
stringWriter.Close();
return stringWriter.ToString();
}
instead? As far as I can see Server.Execute inherits the same http request.

ASP.NET : Hide Querystring in URL

I don't know if I'm just being overly hopeful, but is there a way to hide the query string returned in the URL?
The scenario I am in is where I have page1.aspx redirecting a command to an outside server via a post, and it returns it to page2.aspx. The only problem I have with this, is that the querystring of the returned variables are still left in the URL.
I just want to hide the ugly string/information from the common user. So is there a way to edit and reload that in the pageload method or do I just have to save the variables on a middleman page and then hit page 2.
What is the origin of these querystring variables? Can you not submit all data as POST data, so that there is no querystring?
You could possibly also use
Context.RewritePath("/foo.aspx")
Here's a link to a ScottGu blog post about URL rewriting.
http://weblogs.asp.net/scottgu/archive/2007/02/26/tip-trick-url-rewriting-with-asp-net.aspx
Awhile back I made some http encoding encrypt/decrypt methods for this purpose. Sometimes in asp.net you need to use the query string, but you also need the end user to not know the value. What I do is base 64 encode, encrypt the value, hash the value based on my private key, and stick them together with a -. On the other side I check the left side hash to verify authenticity, and decrypt the right side. One really nice gotcha is that + (which is a valid base64 string value) is equal to space in html encoding, so I take that into account in the decrypt.
The way I use this is add the encrypted value to the query string, and then decrypt it on the other side
private const string KEY = "<random value goes here>";
public static string EncryptAndHash(this string value)
{
MACTripleDES des = new MACTripleDES();
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
des.Key = md5.ComputeHash(Encoding.UTF8.GetBytes(KEY));
string encrypted = Convert.ToBase64String(des.ComputeHash(Encoding.UTF8.GetBytes(value))) + '-' + Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
return HttpUtility.UrlEncode(encrypted);
}
/// <summary>
/// Returns null if string has been modified since encryption
/// </summary>
/// <param name="encoded"></param>
/// <returns></returns>
public static string DecryptWithHash(this string encoded)
{
MACTripleDES des = new MACTripleDES();
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
des.Key = md5.ComputeHash(Encoding.UTF8.GetBytes(KEY));
string decoded = HttpUtility.UrlDecode(encoded);
// in the act of url encoding and decoding, plus (valid base64 value) gets replaced with space (invalid base64 value). this reverses that.
decoded = decoded.Replace(" ", "+");
string value = Encoding.UTF8.GetString(Convert.FromBase64String(decoded.Split('-')[1]));
string savedHash = Encoding.UTF8.GetString(Convert.FromBase64String(decoded.Split('-')[0]));
string calculatedHash = Encoding.UTF8.GetString(des.ComputeHash(Encoding.UTF8.GetBytes(value)));
if (savedHash != calculatedHash) return null;
return value;
}
I don't like this approach, but it will work.
Once you know you are where you need to be you can Response.Redirect to the same page and they will be gone.
It preserves Query String and Form Variables (optionally). It doesn’t show the real URL where it redirects the request in the users web browser. Server.Transfer happens without the browser knowing anything. The browser requests a page, but the server returns the content of another.
protected void btnServer_Click(object sender, EventArgs e)
{
Server.Transfer("~/About.aspx?UserId=2");
}

Resources