How can I use a String in an URL? - rest

I'm trying to create Web Service where I can send a stardog request using a HTTP GET method. My problem is that the stardog request are using a few symbol that aren't allowed in a URL, like ? or ; and I'm trying to not force the user to manually convert it to %3F or %3B.
So I want my URL to look like this :
localhost:8080/WebServiceTest/query?select="SELECT * WHERE {?s ?p ?o}"
My Jersey annotations are the following:
#GET
#Path("/query")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public String execQuery(#QueryParam("select") String selectQuery, #QueryParam("update") String updateQuery) {
Does anyone know if this is possible? And if it is, how can I do that?

If you use Javascript/ECMAScript 5.1++ on client side you can convert a string to a uri string format. Use the method
encodeURIComponent(str);
which is explained here.
It is also possible to replace the ? ; chars by their %-represetation. (In the case of using cUrl)
(SPACE) ! " # $ % & ' ( ) * + , / : ;
%20 %21 %22 %23 %24 %25 %26 %27 %28 %29 %2A %2B %2C %2F %3A %3B
= ? # [ ]
%3D %3F %40 %5B %5D

Related

Deployment Azur function from Business Central

I try to deploy the Azure function by using Rest API and zip-archive of solution.
It works properly in Postman.
I've found advice on how to upload mp3 files and develop a solution for my task.
But when I try to create a payload for request by AL-code for Business Central (file have been uploaded to instr):
CR := 13;
LF := 10;
NewLine += '' + CR + LF;
httpHeader.Clear();
TempBlob.CreateOutStream(PayloadOutStream);
PayloadOutStream.WriteText('--boundary' + NewLine);
PayloadOutStream.WriteText(StrSubstNo('Content-Disposition: form-data; name="file"; filename="%1"', filename) + NewLine);
PayloadOutStream.WriteText('Content-Type: application/zip' + NewLine);
PayloadOutStream.WriteText(NewLine);
CopyStream(PayloadOutStream, InStr);
PayloadOutStream.WriteText(NewLine);
PayloadOutStream.WriteText('--boundary');
PayloadOutStream.WriteText(NewLine);
TempBlob.CreateInStream(PayloadInStream);
Content.WriteFrom(PayloadInStream);
Content.GetHeaders(httpHeader);
if httpHeader.Contains('Content-Type') then httpHeader.Remove('Content-Type');
httpHeader.Add('Content-Type', 'multipart/form-data;boundary=boundary');
httpRequest := CreateHttpRequestMessage(Content, 'Post', RequestURI);
Client.Clear();
Client.DefaultRequestHeaders.Add('Authorization', StrSubstNo('Bearer %1', token));
if Client.Send(httpRequest, httpResponse) then begin
httpResponse.Content().ReadAs(responseText);
Message(responseText);
end
else
Error(RequestErrorMsg);
I received an error in the response message from the deployment process like this:
{"Message":"An error has occurred.","ExceptionMessage":"Number of entries expected in End Of Central Directory does not correspond to number of entries in Central Directory.","ExceptionType":"System.IO.InvalidDataException","StackTrace":" at System.IO.Compression.ZipArchive.ReadCentralDirectory()\r\n at System.IO.Compression.ZipArchive.get_Entries()\r\n at Kudu.Core.Infrastructure.ZipArchiveExtensions.Extract(ZipArchive archive, String directoryName, ITracer tracer, Boolean doNotPreserveFileTime) in C:\\Kudu Files\\Private\\src\\master\\Kudu.Core\\Infrastructure\\ZipArchiveExtensions.cs:line 114\r\n at Kudu.Services.Deployment.PushDeploymentController.<>c__DisplayClass21_0.<LocalZipFetch>b__1() in C:\\Kudu Files\\Private\\src\\master\\Kudu.Services\\Deployment\\PushDeploymentController.cs:line 746\r\n at System.Threading.Tasks.Task.InnerInvoke()\r\n at System.Threading.Tasks.Task.Execute()......
I believe, something is wrong when I build the payload. Could you give me advice on how I have to build the body of request for my case?

authorization for API gateway

I used this tutorial and created "put" endpoint successfully.
https://sanderknape.com/2017/10/creating-a-serverless-api-using-aws-api-gateway-and-dynamodb/
When I follow this advice, I get authroization required error..
Using your favorite REST client, try to PUT an item into DynamoDB
using your API Gateway URL.
python is my favorite client:
import requests
api_url = "https://0pg2858koj.execute-api.us-east-1.amazonaws.com/tds"
PARAMS = {"name": "test", "favorite_movie":"asdsf"}
r = requests.put(url=api_url, params=PARAMS)
the response is 403
My test from console is successful, but not able to put a record from python.
The first step you can take to resolve the problem is to investigate the information returned by AWS in the 403 response. It will provide a header, x-amzn-ErrorType and error message with information about the concrete error. You can test it with curl in verbose mode (-v) or with your Python code. Please, review the relevant documentation to obtain a detailed enumeration of all the possible error reasons.
In any case, looking at your code, it is very likely that you did not provide the necessary authentication or authorization information to AWS.
The kind of information that you must provide depends on which mechanism you configured to access your REST API in API Gateway.
If, for instance, you configured IAM based authentication, you need to set up your Python code to generate an Authorization header with an AWS Signature derived from your user access key ID and associated secret key. The AWS documentation provides an example of use with Postman.
The AWS documentation also provides several examples of how to use python and requests to perform this kind of authorization.
Consider, for instance, this example for posting information to DynamoDB:
# Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# This file is licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License. A copy of the
# License is located at
#
# http://aws.amazon.com/apache2.0/
#
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
# OF ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
# AWS Version 4 signing example
# DynamoDB API (CreateTable)
# See: http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
# This version makes a POST request and passes request parameters
# in the body (payload) of the request. Auth information is passed in
# an Authorization header.
import sys, os, base64, datetime, hashlib, hmac
import requests # pip install requests
# ************* REQUEST VALUES *************
method = 'POST'
service = 'dynamodb'
host = 'dynamodb.us-west-2.amazonaws.com'
region = 'us-west-2'
endpoint = 'https://dynamodb.us-west-2.amazonaws.com/'
# POST requests use a content type header. For DynamoDB,
# the content is JSON.
content_type = 'application/x-amz-json-1.0'
# DynamoDB requires an x-amz-target header that has this format:
# DynamoDB_<API version>.<operationName>
amz_target = 'DynamoDB_20120810.CreateTable'
# Request parameters for CreateTable--passed in a JSON block.
request_parameters = '{'
request_parameters += '"KeySchema": [{"KeyType": "HASH","AttributeName": "Id"}],'
request_parameters += '"TableName": "TestTable","AttributeDefinitions": [{"AttributeName": "Id","AttributeType": "S"}],'
request_parameters += '"ProvisionedThroughput": {"WriteCapacityUnits": 5,"ReadCapacityUnits": 5}'
request_parameters += '}'
# Key derivation functions. See:
# http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python
def sign(key, msg):
return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()
def getSignatureKey(key, date_stamp, regionName, serviceName):
kDate = sign(('AWS4' + key).encode('utf-8'), date_stamp)
kRegion = sign(kDate, regionName)
kService = sign(kRegion, serviceName)
kSigning = sign(kService, 'aws4_request')
return kSigning
# Read AWS access key from env. variables or configuration file. Best practice is NOT
# to embed credentials in code.
access_key = os.environ.get('AWS_ACCESS_KEY_ID')
secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
if access_key is None or secret_key is None:
print('No access key is available.')
sys.exit()
# Create a date for headers and the credential string
t = datetime.datetime.utcnow()
amz_date = t.strftime('%Y%m%dT%H%M%SZ')
date_stamp = t.strftime('%Y%m%d') # Date w/o time, used in credential scope
# ************* TASK 1: CREATE A CANONICAL REQUEST *************
# http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
# Step 1 is to define the verb (GET, POST, etc.)--already done.
# Step 2: Create canonical URI--the part of the URI from domain to query
# string (use '/' if no path)
canonical_uri = '/'
## Step 3: Create the canonical query string. In this example, request
# parameters are passed in the body of the request and the query string
# is blank.
canonical_querystring = ''
# Step 4: Create the canonical headers. Header names must be trimmed
# and lowercase, and sorted in code point order from low to high.
# Note that there is a trailing \n.
canonical_headers = 'content-type:' + content_type + '\n' + 'host:' + host + '\n' + 'x-amz-date:' + amz_date + '\n' + 'x-amz-target:' + amz_target + '\n'
# Step 5: Create the list of signed headers. This lists the headers
# in the canonical_headers list, delimited with ";" and in alpha order.
# Note: The request can include any headers; canonical_headers and
# signed_headers include those that you want to be included in the
# hash of the request. "Host" and "x-amz-date" are always required.
# For DynamoDB, content-type and x-amz-target are also required.
signed_headers = 'content-type;host;x-amz-date;x-amz-target'
# Step 6: Create payload hash. In this example, the payload (body of
# the request) contains the request parameters.
payload_hash = hashlib.sha256(request_parameters.encode('utf-8')).hexdigest()
# Step 7: Combine elements to create canonical request
canonical_request = method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash
# ************* TASK 2: CREATE THE STRING TO SIGN*************
# Match the algorithm to the hashing algorithm you use, either SHA-1 or
# SHA-256 (recommended)
algorithm = 'AWS4-HMAC-SHA256'
credential_scope = date_stamp + '/' + region + '/' + service + '/' + 'aws4_request'
string_to_sign = algorithm + '\n' + amz_date + '\n' + credential_scope + '\n' + hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()
# ************* TASK 3: CALCULATE THE SIGNATURE *************
# Create the signing key using the function defined above.
signing_key = getSignatureKey(secret_key, date_stamp, region, service)
# Sign the string_to_sign using the signing_key
signature = hmac.new(signing_key, (string_to_sign).encode('utf-8'), hashlib.sha256).hexdigest()
# ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
# Put the signature information in a header named Authorization.
authorization_header = algorithm + ' ' + 'Credential=' + access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature
# For DynamoDB, the request can include any headers, but MUST include "host", "x-amz-date",
# "x-amz-target", "content-type", and "Authorization". Except for the authorization
# header, the headers must be included in the canonical_headers and signed_headers values, as
# noted earlier. Order here is not significant.
# # Python note: The 'host' header is added automatically by the Python 'requests' library.
headers = {'Content-Type':content_type,
'X-Amz-Date':amz_date,
'X-Amz-Target':amz_target,
'Authorization':authorization_header}
# ************* SEND THE REQUEST *************
print('\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++')
print('Request URL = ' + endpoint)
r = requests.post(endpoint, data=request_parameters, headers=headers)
print('\nRESPONSE++++++++++++++++++++++++++++++++++++')
print('Response code: %d\n' % r.status_code)
print(r.text)
I think it could be easily adapted to your needs.
In the console, everything works fine because when you invoke your REST endpoints in API Gateway, you are connected to a user who is already authenticated and authorized to access these REST endpoints.

How to fix unterminated short string error in Varnish VCL while configuring Fastly CDN

I am trying to setup token based authentication on Fastly CDN with Varnish VCL and using this sample VCL snippet to generate and validate JWT tokens here -
sub vcl_recv {
#FASTLY recv
if (req.request != "HEAD" && req.request != "GET" && req.request != "FASTLYPURGE") {
return(pass);
}
// Generate synth
if(req.url ~ "generate") {
error 901;
}
// Validate token
if(req.url ~ "validate") {
// Ensure token exists and parse into regex
if (req.http.X-JWT !~ "^([a-zA-Z0-9\-_]+)?\.([a-zA-Z0-9\-_]+)?\.([a-zA-Z0-9\-_]+)?$") {
// Forbidden
error 403 "Forbidden";
}
// Extract token header, payload and signature
set req.http.X-JWT-Header = re.group.1;
set req.http.X-JWT-Payload = re.group.2;
set req.http.X-JWT-Signature = digest.base64url_nopad_decode(re.group.3);
set req.http.X-JWT-Valid-Signature = digest.hmac_sha256("SupSecretStr",
req.http.X-JWT-Header "." req.http.X-JWT-Payload);
// Validate signature
if(digest.secure_is_equal(req.http.X-JWT-Signature, req.http.X-JWT-Valid-Signature)) {
// Decode payload
set req.http.X-JWT-Payload = digest.base64url_nopad_decode(req.http.X-JWT-Payload);
set req.http.X-JWT-Expires = regsub(req.http.X-JWT-Payload, {"^.*?"exp"\s*?:\s*?([0-9]+).*?$"}, "\1");
// Validate expiration
if (time.is_after(now, std.integer2time(std.atoi(req.http.X-JWT-Expires)))) {
// Unauthorized
synthetic {"{"sign":""} req.http.X-JWT-Signature {"","header":""} req.http.X-JWT-Header {"","payload":""} req.http.X-JWT-Payload {"","valid": ""} req.http.X-JWT-Valid-Signature {""}"};
return(deliver);
}
// OK
synthetic {"{"header2":""} req.http.X-JWT-Header {"","payload":""} req.http.X-JWT-Payload {"","sign":""} req.http.X-JWT-Signature {"","valid": ""} req.http.X-JWT-Valid-Signature {""}"};
return(deliver);
} else {
// Forbidden
synthetic {"{"header3":""} req.http.X-JWT-Header {"","payload":""} req.http.X-JWT-Payload {"","sign":""} req.http.X-JWT-Signature {"","valid": ""} req.http.X-JWT-Valid-Signature {""}"};
return(deliver);
}
}
return(lookup);
}
sub vcl_error {
#FASTLY error
// Generate JWT token
if (obj.status == 901) {
set obj.status = 200;
set obj.response = "OK";
set obj.http.Content-Type = "application/json";
set obj.http.X-UUID = randomstr(8, "0123456789abcdef") "-" randomstr(4, "0123456789abcdef") "-4" randomstr(3, "0123456789abcdef") "-" randomstr(1, "89ab") randomstr(3, "0123456789abcdef") "-" randomstr(12, "0123456789abcdef");
set obj.http.X-JWT-Issued = now.sec;
set obj.http.X-JWT-Expires = strftime({"%s"}, time.add(now, 3600s));
set obj.http.X-JWT-Header = digest.base64url_nopad({"{"alg":"HS256","typ":"JWT""}{"}"});
set obj.http.X-JWT-Payload = digest.base64url_nopad({"{"sub":""} obj.http.X-UUID {"","exp":"} obj.http.X-JWT-Expires {","iat":"} obj.http.X-JWT-Issued {","iss":"Fastly""}{"}"});
set obj.http.X-JWT-Signature = digest.base64url_nopad(digest.hmac_sha256("SupSecretStr", obj.http.X-JWT-Header "." obj.http.X-JWT-Payload));
set obj.http.X-JWT = obj.http.X-JWT-Header "." obj.http.X-JWT-Payload "." obj.http.X-JWT-Signature;
unset obj.http.X-UUID;
unset obj.http.X-JWT-Issued;
unset obj.http.X-JWT-Expires;
unset obj.http.X-JWT-Header;
unset obj.http.X-JWT-payload;
unset obj.http.X-JWT-Signature;
synthetic {"{"payload":""} obj.http.X-JWT-Payload {"","header":""} obj.http.X-JWT-Header {"","sign":""} obj.http.X-JWT-Signatre {"","token": ""} obj.http.X-JWT {""}"};
return(deliver);
}
// Valid token
if (obj.status == 902) {
set obj.status = 200;
set obj.response = "OK";
set obj.http.Content-Type = "application/json";
synthetic {"{ "token": ""} req.http.X-JWT {"" }"};
return(deliver);
}
}
Now, when I am trying to compile this it returns -
Syntax error: Unterminated _short-string_
at: (input Line 106 Pos 197)
synthetic {"{"sign":""} req.http.X-JWT-Signature {"","header":""} req.http.X-JWT-Header {"","payload":""} req.http.X-JWT-Payload {"","valid": ""} req.http.X-JWT-Valid-Signature {""}"};
Looks like i am not somehow escaping the values correctly here during the synthetic block.
The only reason I am trying to do add this synthetic block in the vcl_recv subroutine is because I want to test how the digest is generating the JWT token and validating it and with that I wanted to create similar JWT tokens on server side in Node.Js so i was trying to output the different intermediate parts of the token for debugging.
I am not quite familiar with the Varnish syntax and semantics but still I looked for help finding any docs regarding this schedule subroutine but found none so far.
So, can anyone help out in how to fix this and have the vcl_recv, vcl_error interpolate different intermediate values in the json response.
I tried to use some of the Node.Js based base64 url decode libraries to decode the returned token parts and was able to decode the header and payload part but the signature part I am not able to generate from the Node.Js. So, can anyone suggest what is the equivalent of base64url_nopad() in node.js or any javascript libraries ?
For the hmac_256 encryption part we are trying to use the crypto library and creating an hmac like crypto.createHmac('sha256', 'SupSecretStr').update().digest('hex'); but all base64 encode url libraries in js i think return padded urls which is why the base64 encoded part of this hmac 256 digest doesn't match the one generated with varnish
My syntax coloring tool is telling me pretty much the same thing as the error message: you screwed up your quotes :-)
Your last block {""}"}; is opening quotes ({"), immediately closing them ("}), then you are opening simple quotes " and the newline arrives before you close them.
To fix, just put a space between after the final quote of the json: {"" }"};

Apiary - Howto change the format of cURL call

In Apiary, the cURL call to production by default is :
https://example.com/v1/findBrandCat?matchstring=&interestType=
I have to make a call in following structure:
https://example.com/v1/findBrandCat/matchstringVALUE/interestTypeVALUE
How to make it?
A URI template for the API resource can be defined as follows:
# GET /v1/findBrandCat/{matchstringValue}/{interestTypeValue}
+ Parameters
+ matchstringValue: (required, string)
+ interestTypeValue: (required, string)
+ Response 200 (application/json)

Classic ASP amazon s3 rest authorisation

I am confused on what I am doing wrong here...
<script language="javascript" runat="server">
function GMTNow(){return new Date().toGMTString()}
</script>
<%
Const AWS_BUCKETNAME = "uk-bucketname"
Const AWS_ACCESSKEY = "GOES HERE"
Const AWS_SECRETKEY = "SECRET"
LocalFile = Server.Mappath("/test.jpg")
Dim sRemoteFilePath
sRemoteFilePath = "/files/test.jpg" 'Remote Path, note that AWS paths (in fact they aren't real paths) are strictly case sensitive
Dim strNow
strNow = GMTNow() ' GMT Date String
Dim StringToSign
StringToSign = Replace("PUT\n\nimage/jpeg\n\nx-amz-date:" & strNow & "\n/"& AWS_BUCKETNAME & sRemoteFilePath, "\n", vbLf)
Dim Signature
Signature = BytesToBase64(HMACSHA1(AWS_SECRETKEY, StringToSign))
Dim Authorization
Authorization = "AWS " & AWS_ACCESSKEY & ":" & Signature
Dim AWSBucketUrl
AWSBucketUrl = "http://s3.amazonaws.com/" & AWS_BUCKETNAME
With Server.CreateObject("Microsoft.XMLHTTP")
.open "PUT", AWSBucketUrl & sRemoteFilePath, False
.setRequestHeader "Authorization", Authorization
.setRequestHeader "Content-Type", "image/jpeg"
.setRequestHeader "Host", AWS_BUCKETNAME & ".s3.amazonaws.com"
.setRequestHeader "x-amz-date", strNow
.send GetBytes(LocalFile) 'Get bytes of local file and send
If .status = 200 Then ' successful
Response.Write "<a href="& AWSBucketUrl & sRemoteFilePath &" target=_blank>Uploaded File</a>"
Else ' an error ocurred, consider xml string of error details
Response.ContentType = "text/xml"
Response.Write .responseText
End If
End With
Function GetBytes(sPath)
dim fs,f
set fs=Server.CreateObject("Scripting.FileSystemObject")
set f=fs.GetFile(sPath)
GetBytes = f.Size
set f=nothing
set fs=nothing
End Function
Function BytesToBase64(varBytes)
With Server.CreateObject("MSXML2.DomDocument").CreateElement("b64")
.dataType = "bin.base64"
.nodeTypedValue = varBytes
BytesToBase64 = .Text
End With
End Function
Function HMACSHA1(varKey, varValue)
With Server.CreateObject("System.Security.Cryptography.HMACSHA1")
.Key = UTF8Bytes(varKey)
HMACSHA1 = .ComputeHash_2(UTF8Bytes(varValue))
End With
End Function
Function UTF8Bytes(varStr)
With Server.CreateObject("System.Text.UTF8Encoding")
UTF8Bytes = .GetBytes_4(varStr)
End With
End Function
%>
Now getting the error.
msxml3.dll error '800c0008'
The download of the specified resource has failed.
/s3.asp, line 39
I'd like to explain how S3 Rest Api works as far as I know.First, you need to learn what should be the string to sign Amazon accepts.
Format :
StringToSign = HTTP-Verb + "\n" +
Content-MD5 + "\n" +
Content-Type + "\n" +
Date + "\n" +
CanonicalizedAmzHeaders +
CanonicalizedResource;
Generating signed string :
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
Passing authorization header:
Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
Unfortunately you'll play byte to byte since there is no any SDK released for classic asp. So, should understand by reading the entire page http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
For string to sign as you can see above in format, there are three native headers are reserved by the API. Content-Type, Content-MD5 and Date. These headers must be exists in the string to sign even your request hasn't them as empty without header name, just its value. There is an exception, Date header must be empty in string to sign if x-amz-date header is already exists in the request. Then, If request has canonical amazon headers, you should add them as key-value pairs like x-amz-headername:value. But, there is another exception need to be considered for multiple headers. Multiple headers should combine to one header with values comma separated.
Correct
x-amz-headername:value1,value2
Wrong
x-amz-headername:value1\n
x-amz-headername:value2
Most importantly, headers must be ascending order by its group in the string to sign. First, reserved headers with ascending order, then canonical headers with ascending order.
I'd recommend using DomDocument functionality to generate Base64 encoded strings.
Additionally instead of a Windows Scripting Component (.wsc files), you could use .Net's interops such as System.Security.Cryptography to generating keyed hashes more effectively with power of System.Text. All of these interoperabilities are available in today's IIS web servers.
So, as an example I wrote the below script just sends a file to bucket you specified. Consider and test it.
Assumed local file name is myimage.jpg and will be uploaded with same name to root of the bucket.
<script language="javascript" runat="server">
function GMTNow(){return new Date().toGMTString()}
</script>
<%
Const AWS_BUCKETNAME = "uk-bucketname"
Const AWS_ACCESSKEY = "GOES HERE"
Const AWS_SECRETKEY = "SECRET"
LocalFile = Server.Mappath("/test.jpg")
Dim sRemoteFilePath
sRemoteFilePath = "/files/test.jpg" 'Remote Path, note that AWS paths (in fact they aren't real paths) are strictly case sensitive
Dim strNow
strNow = GMTNow() ' GMT Date String
Dim StringToSign
StringToSign = Replace("PUT\n\nimage/jpeg\n\nx-amz-date:" & strNow & "\n/"& AWS_BUCKETNAME & sRemoteFilePath, "\n", vbLf)
Dim Signature
Signature = BytesToBase64(HMACSHA1(AWS_SECRETKEY, StringToSign))
Dim Authorization
Authorization = "AWS " & AWS_ACCESSKEY & ":" & Signature
Dim AWSBucketUrl
AWSBucketUrl = "https://" & AWS_BUCKETNAME & ".s3.amazonaws.com"
With Server.CreateObject("MSXML2.ServerXMLHTTP.6.0")
.open "PUT", AWSBucketUrl & sRemoteFilePath, False
.setRequestHeader "Authorization", Authorization
.setRequestHeader "Content-Type", "image/jpeg"
.setRequestHeader "Host", AWS_BUCKETNAME & ".s3.amazonaws.com"
.setRequestHeader "x-amz-date", strNow
.send GetBytes(LocalFile) 'Get bytes of local file and send
If .status = 200 Then ' successful
Response.Write "<a href="& AWSBucketUrl & sRemoteFilePath &" target=_blank>Uploaded File</a>"
Else ' an error ocurred, consider xml string of error details
Response.ContentType = "text/xml"
Response.Write .responseText
End If
End With
Function GetBytes(sPath)
With Server.CreateObject("Adodb.Stream")
.Type = 1 ' adTypeBinary
.Open
.LoadFromFile sPath
.Position = 0
GetBytes = .Read
.Close
End With
End Function
Function BytesToBase64(varBytes)
With Server.CreateObject("MSXML2.DomDocument").CreateElement("b64")
.dataType = "bin.base64"
.nodeTypedValue = varBytes
BytesToBase64 = .Text
End With
End Function
Function HMACSHA1(varKey, varValue)
With Server.CreateObject("System.Security.Cryptography.HMACSHA1")
.Key = UTF8Bytes(varKey)
HMACSHA1 = .ComputeHash_2(UTF8Bytes(varValue))
End With
End Function
Function UTF8Bytes(varStr)
With Server.CreateObject("System.Text.UTF8Encoding")
UTF8Bytes = .GetBytes_4(varStr)
End With
End Function
%>
The Amazon Signature must be url encoded in a slightly different way to what VBSCript encodes. The following function will encode the result correctly:
JScript Version:
function amazonEncode(s)
{
return Server.UrlEncode(s).replace(/\+/g,"%20").replace(/\%2E/g,".").replace(/\%2D/g,"-").replace(/\%7E/g,"~").replace(/\%5F/g,"_");
}
VBScript Version:
function amazonEncode(s)
dim retval
retval = Server.UrlEncode(s)
retval = replace(retval,"+","%20")
retval = replace(retval,"%2E",".")
retval = replace(retval,"%2D","-")
retval = replace(retval,"%7E","~")
retval = replace(retval,"%5F","_")
amazonEncode = retval
end function
As for base64, I used .NET's already built functionality for it. I had to create a DLL to wrap it, so that I could use it from JScript (or VBScript).
Here's how to create that dll:
Download the free C# 2010 Express and install it.
You also need to use two other tools that you won’t have a path to, so you will need to add the path to your PATH environment variable, so at a cmd prompt search for regasm.exe, guidgen.exe and sn.exe (you might find several versions – select the one with the latest date).
• cd\
• dir/s regasm.exe
• dir/s sn.exe
• dir/s guidgen.exe
So as an example, a COM object that has just one method which just returns “Hello”:
Our eventual aim is to use it like this:
<%#Language=JScript%>
<%
var x = Server.CreateObject("blah.whatever");
Response.Write(x.someMethod());
%>
or
<%#Language=VBScript%>
<%
dim x
set x = Server.CreateObject("blah.whatever")
Response.Write x.someMethod()
%>
• Start C# and create a new project
• Select “Empty Project”
• Give it a name – this becomes the namespace by default (the blah in the sample above)
• Next save the project (so you know where to go for the next bit). This will create a folder structure like so:
o blah this contains your solution files that the editor needs (blah.sln etc)
 blah this contains your source code and project files
• bin
o Debug the compiled output ends up here
• Next, using the cmd console, navigate to the root blah folder and create a key pair file:
sn –k key.snk
• Next you need a unique guid (enter guidgen at the cmd prompt)
o Select registry format
o Click “New Guid”
o Click “Copy”
• Back to C# editor – from the menu, select Project – Add Class
• Give it a name – this is the whatever in the sample above
• After the opening brace just after the namespace line type:
[GuidAttribute(“paste your guid here”)]
remove the curly brackets from your pasted guid
• You will need to add another “using” at the top
using System.Runtime.InteropServices;
• Finally you need to create someMethod
The final C# code looks like this (the bits in red may be different in your version):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace blah
{
[GuidAttribute("AEF4F27F-9E97-4189-9AD5-64386A1699A7")]
public class whatever
{
public string someMethod()
{
return "Hello";
}
}
}
• Next, from the menu, select Project – Properties
o On the left, select Application and, for the Output type dropdown, select “Class Library”
o On the left, select Signing and tick the “Sign the assembly” box, then browse to the key.snk file you made earlier
o Save the properties (CTRL-S)
• Next build the dll (Press F6) – This will create a dll in the Debug folder
• Open a cmd window as administrator (right click cmd.exe and select “Run as Administrator”)
• Navigate to the Debug folder and enter the following to register the assembly:
regasm blah.dll /tlb:blah.tlb /codebase blah
That’s it – the above is a genuine COM component and will work in other applications, the example below allows for event handling and only really works in ASP due to the default property mechanism of ASP:
The code for the base64 stuff would be:
// returns a base 64 encoded string that has been encrypted with SHA256
// parameters:
// s string to encrypt
// k key to use during encryption
public string getBase64SHA256(string s, string k)
{
HMACSHA256 sha = new HMACSHA256();
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
sha.Key = encoding.GetBytes(k);
byte[] hashBytes = sha.ComputeHash(encoding.GetBytes(s));
return System.Convert.ToBase64String(hashBytes);
}
// returns a base 64 encoded string that has been encrypted with SHA1
// parameters:
// s string to encrypt
// k key to use during encryption
public string getBase64SHA1(string s, string k)
{
HMACSHA1 sha = new HMACSHA1();
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
sha.Key = encoding.GetBytes(k);
byte[] hashBytes = sha.ComputeHash(encoding.GetBytes(s));
return System.Convert.ToBase64String(hashBytes);
}
You would need the relevant usings:
using System.Security.Cryptography;
The signature in full must have all the query string name-value pairs in alphabetical order before computing the SHA and base64. Here is my version of the signature creator function:
function buildAmazonSignature(host,req,qstring)
{
var str="", i, arr = String(qstring).split("&");
for (i=0; i<arr.length; i++)
arr[i] = arr[i].split("=");
arr.sort(amazonSortFunc);
for (i=0; i<arr.length; i++)
{
if (str != "")
str += "&";
str += arr[i][0] + "=" + arr[i][1];
}
str = "GET\n"+host+"\n"+req+"\n"+str;
var utils = Server.CreateObject("FMAG.Utils");
var b64 = utils.getBase64SHA256(str, "xxxxxxxxxx");
utils = null;
return amazonEncode(b64);
}
function amazonSortFunc(a,b)
{
return (a[0]<b[0])?-1:((a[0]>b[0])?1:0);
}
VBScript doesn't have a very good array sort facility, so you'll have to work that one out yourself - sorry
Also I have the timestamp in this format:
YYYY-MM-DDTHH:MM:SSZ
Also the stuff in the query string included the following:
AWSAccessKeyId
SignatureMethod
SignatureVersion
Version
Expires
Action
Hope that helps
Thank you so much for this question, it has been such a great help to start my WSH/VBScript for my S3 backup service ;-)
I do not have much time, so I will not go through the details of the things I have changed from Chris' code, but please find below my little prototype script which works perfectly ;-)
This is just a WSH/VBScript, so you do not need IIS to run it, you just need to paste the content in a file with the ".vbs" extension, and you can then directly execute it ;-)
Option Explicit
'-- Amazon Web Services > My Account > Access Credentials > Access Keys --'
Dim strAccessKeyID: strAccessKeyID = "..."
Dim strSecretAccessKey: strSecretAccessKey = "..."
'-- Parameters: --'
Dim strLocalFile: strLocalFile = "..."
Dim strRemoteFile: strRemoteFile = "..."
Dim strBucket: strBucket = "..."
'-- Authentication: --'
Dim strNowInGMT: strNowInGMT = NowInGMT()
Dim strStringToSign: strStringToSign = _
"PUT" & vbLf & _
"" & vbLf & _
"text/xml" & vbLf & _
strNowInGMT & vbLf & _
"/" & strBucket + "/" & strRemoteFile
Dim strSignature: strSignature = ConvertBytesToBase64(HMACSHA1(strSecretAccessKey, strStringToSign))
Dim strAuthorization: strAuthorization = "AWS " & strAccessKeyID & ":" & strSignature
'-- Upload: --'
Dim xhttp: Set xhttp = CreateObject("MSXML2.ServerXMLHTTP")
xhttp.open "PUT", "http://" & strBucket & ".s3.amazonaws.com/" & strRemoteFile, False
xhttp.setRequestHeader "Content-Type", "text/xml"
xhttp.setRequestHeader "Date", strNowInGMT 'Yes, this line is mandatory ;-) --'
xhttp.setRequestHeader "Authorization", strAuthorization
xhttp.send GetBytesFromFile(strLocalFile)
If xhttp.status = "200" Then
WScript.Echo "The file has been successfully uploaded ;-)"
Else
WScript.Echo "There was an error :-(" & vbCrLf & vbCrLf & _
xhttp.responseText
End If
Set xhttp = Nothing
'-- NowInGMT ------------------------------------------------------------------'
Function NowInGMT()
'This is probably not the best implementation, but it works ;-) --'
Dim sh: Set sh = WScript.CreateObject("WScript.Shell")
Dim iOffset: iOffset = sh.RegRead("HKLM\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias")
Dim dtNowGMT: dtNowGMT = DateAdd("n", iOffset, Now())
Dim strDay: strDay = "NA"
Select Case Weekday(dtNowGMT)
Case 1 strDay = "Sun"
Case 2 strDay = "Mon"
Case 3 strDay = "Tue"
Case 4 strDay = "Wed"
Case 5 strDay = "Thu"
Case 6 strDay = "Fri"
Case 7 strDay = "Sat"
Case Else strDay = "Error"
End Select
Dim strMonth: strMonth = "NA"
Select Case Month(dtNowGMT)
Case 1 strMonth = "Jan"
Case 2 strMonth = "Feb"
Case 3 strMonth = "Mar"
Case 4 strMonth = "Apr"
Case 5 strMonth = "May"
Case 6 strMonth = "Jun"
Case 7 strMonth = "Jul"
Case 8 strMonth = "Aug"
Case 9 strMonth = "Sep"
Case 10 strMonth = "Oct"
Case 11 strMonth = "Nov"
Case 12 strMonth = "Dec"
Case Else strMonth = "Error"
End Select
Dim strHour: strHour = CStr(Hour(dtNowGMT))
If Len(strHour) = 1 Then strHour = "0" & strHour End If
Dim strMinute: strMinute = CStr(Minute(dtNowGMT))
If Len(strMinute) = 1 Then strMinute = "0" & strMinute End If
Dim strSecond: strSecond = CStr(Second(dtNowGMT))
If Len(strSecond) = 1 Then strSecond = "0" & strSecond End If
Dim strNowInGMT: strNowInGMT = _
strDay & _
", " & _
Day(dtNowGMT) & _
" " & _
strMonth & _
" " & _
Year(dtNowGMT) & _
" " & _
strHour & _
":" & _
strMinute & _
":" & _
strSecond & _
" +0000"
NowInGMT = strNowInGMT
End Function
'-- GetBytesFromString --------------------------------------------------------'
Function GetBytesFromString(strValue)
Dim stm: Set stm = CreateObject("ADODB.Stream")
stm.Open
stm.Type = 2
stm.Charset = "ascii"
stm.WriteText strValue
stm.Position = 0
stm.Type = 1
GetBytesFromString = stm.Read
Set stm = Nothing
End Function
'-- HMACSHA1 ------------------------------------------------------------------'
Function HMACSHA1(strKey, strValue)
Dim sha1: Set sha1 = CreateObject("System.Security.Cryptography.HMACSHA1")
sha1.key = GetBytesFromString(strKey)
HMACSHA1 = sha1.ComputeHash_2(GetBytesFromString(strValue))
Set sha1 = Nothing
End Function
'-- ConvertBytesToBase64 ------------------------------------------------------'
Function ConvertBytesToBase64(byteValue)
Dim dom: Set dom = CreateObject("MSXML2.DomDocument")
Dim elm: Set elm = dom.CreateElement("b64")
elm.dataType = "bin.base64"
elm.nodeTypedValue = byteValue
ConvertBytesToBase64 = elm.Text
Set elm = Nothing
Set dom = Nothing
End Function
'-- GetBytesFromFile ----------------------------------------------------------'
Function GetBytesFromFile(strFileName)
Dim stm: Set stm = CreateObject("ADODB.Stream")
stm.Type = 1 'adTypeBinary --'
stm.Open
stm.LoadFromFile strFileName
stm.Position = 0
GetBytesFromFile = stm.Read
stm.Close
Set stm = Nothing
End Function
Dear stone-edge-technology-VBScript-mates (*), let me know if it is working for you as well ;-)
(*) This is a reference to the comment from Spudley, see above ;-)