Struct qiniu_upload_token::UploadPolicy
source · pub struct UploadPolicy { /* private fields */ }
Expand description
上传策略
可以阅读 https://developer.qiniu.com/kodo/manual/1206/put-policy 了解七牛安全机制。
根据指定的存储空间和对象名称,生成可以用来上传低频存储类型文件的上传策略
use qiniu_upload_token::{FileType, UploadPolicy};
use std::time::Duration;
let upload_policy = UploadPolicy::new_for_object("your-bucket", "your-key", Duration::from_secs(3600))
.file_type(FileType::InfrequentAccess)
.build();
Implementations§
source§impl UploadPolicy
impl UploadPolicy
sourcepub fn new_for_bucket(
bucket: impl Into<BucketName>,
upload_token_lifetime: Duration
) -> UploadPolicyBuilder
pub fn new_for_bucket(
bucket: impl Into<BucketName>,
upload_token_lifetime: Duration
) -> UploadPolicyBuilder
为指定的存储空间生成的上传策略
允许用户上传文件到指定的存储空间,不限制上传客户端指定对象名称。
上传策略根据给出的客户端配置指定上传凭证有效期
sourcepub fn new_for_object(
bucket: impl Into<BucketName>,
object: impl Into<ObjectName>,
upload_token_lifetime: Duration
) -> UploadPolicyBuilder
pub fn new_for_object(
bucket: impl Into<BucketName>,
object: impl Into<ObjectName>,
upload_token_lifetime: Duration
) -> UploadPolicyBuilder
为指定的存储空间和对象名称生成的上传策略
允许用户以指定的对象名称上传文件到指定的存储空间。 上传客户端不能指定与上传策略冲突的对象名称。
上传策略根据给出的客户端配置指定上传凭证有效期
sourcepub fn new_for_objects_with_prefix(
bucket: impl Into<BucketName>,
prefix: impl AsRef<str>,
upload_token_lifetime: Duration
) -> UploadPolicyBuilder
pub fn new_for_objects_with_prefix(
bucket: impl Into<BucketName>,
prefix: impl AsRef<str>,
upload_token_lifetime: Duration
) -> UploadPolicyBuilder
为指定的存储空间和对象名称前缀生成的上传策略
允许用户以指定的对象名称前缀上传文件到指定的存储空间。 上传客户端指定包含该前缀的对象名称。
上传策略根据给出的客户端配置指定上传凭证有效期
sourcepub fn bucket(&self) -> Option<&str>
pub fn bucket(&self) -> Option<&str>
存储空间约束
Examples found in repository?
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
fn bucket_name(&self, opts: GetPolicyOptions) -> ParseResult<BucketName> {
self.policy(opts).and_then(|policy| {
policy
.bucket()
.map_or(Err(ParseError::InvalidUploadTokenFormat), |bucket_name| {
Ok(BucketName::from(bucket_name))
})
})
}
/// 异步获取上传凭证中的存储空间名称
#[cfg(feature = "async")]
#[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
fn async_bucket_name(&self, opts: GetPolicyOptions) -> AsyncParseResult<'_, BucketName> {
Box::pin(async move {
self.async_policy(opts).await.and_then(|policy| {
policy
.bucket()
.map_or(Err(ParseError::InvalidUploadTokenFormat), |bucket_name| {
Ok(BucketName::from(bucket_name))
})
})
})
}
sourcepub fn use_prefixal_object_key(&self) -> bool
pub fn use_prefixal_object_key(&self) -> bool
是否是对象名称前缀约束
sourcepub fn is_insert_only(&self) -> bool
pub fn is_insert_only(&self) -> bool
是否仅允许新增对象,不允许覆盖对象
sourcepub fn mime_detection_enabled(&self) -> bool
pub fn mime_detection_enabled(&self) -> bool
是否启用 MIME 类型自动检测
sourcepub fn token_deadline(&self) -> Option<SystemTime>
pub fn token_deadline(&self) -> Option<SystemTime>
上传凭证过期时间
sourcepub fn return_url(&self) -> Option<&str>
pub fn return_url(&self) -> Option<&str>
Web 端文件上传成功后,浏览器执行 303 跳转的 URL
sourcepub fn return_body(&self) -> Option<&str>
pub fn return_body(&self) -> Option<&str>
上传成功后,自定义七牛云最终返回给上传端的数据
sourcepub fn callback_urls(&self) -> Option<Split<'_, char>>
pub fn callback_urls(&self) -> Option<Split<'_, char>>
上传成功后,七牛云向业务服务器发送 POST 请求的 URL 列表
sourcepub fn callback_host(&self) -> Option<&str>
pub fn callback_host(&self) -> Option<&str>
上传成功后,七牛云向业务服务器发送回调请求时的 Host
sourcepub fn callback_body_type(&self) -> Option<&str>
pub fn callback_body_type(&self) -> Option<&str>
上传成功后,七牛云向业务服务器发送回调请求时的 Content-Type
默认为 application/x-www-form-urlencoded
,也可设置为 application/json
sourcepub fn is_save_key_forced(&self) -> bool
pub fn is_save_key_forced(&self) -> bool
是否忽略客户端指定的对象名称,强制使用自定义对象名称进行文件命名
sourcepub fn file_size_limitation(&self) -> (Option<u64>, Option<u64>)
pub fn file_size_limitation(&self) -> (Option<u64>, Option<u64>)
限定上传文件尺寸的范围
返回的第一个元素为最小尺寸,第二个元素为最大尺寸,如果为 None
表示不限制,单位为字节
sourcepub fn mime_types(&self) -> Option<Split<'_, char>>
pub fn mime_types(&self) -> Option<Split<'_, char>>
限定用户上传的文件类型
指定本字段值,七牛服务器会侦测文件内容以判断 MIME 类型,再用判断值跟指定值进行匹配, 匹配成功则允许上传,匹配失败则返回 403 状态码
sourcepub fn object_lifetime(&self) -> Option<Duration>
pub fn object_lifetime(&self) -> Option<Duration>
对象生命周期
精确到天
sourcepub fn as_json(&self) -> String
pub fn as_json(&self) -> String
获取 JSON 格式的上传凭证
Examples found in repository?
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
fn to_token_string(&self, _opts: ToStringOptions) -> ToStringResult<Cow<'_, str>> {
Ok(Cow::Owned(
self.credential
.get(Default::default())?
.sign_with_data(self.upload_policy.as_json().as_bytes()),
))
}
}
type OnPolicyGeneratedCallback = Arc<dyn Fn(&mut UploadPolicyBuilder) -> AnyResult<()> + Sync + Send + 'static>;
/// 基于存储空间的动态生成
///
/// 根据存储空间的快速生成上传凭证实例
#[derive(Clone)]
pub struct BucketUploadTokenProvider<C: Clone> {
bucket: BucketName,
upload_token_lifetime: Duration,
credential: C,
on_policy_generated: Option<OnPolicyGeneratedCallback>,
}
impl<C: Clone> BucketUploadTokenProvider<C> {
/// 基于存储空间和认证信息动态生成上传凭证实例
#[inline]
pub fn new(bucket: impl Into<BucketName>, upload_token_lifetime: Duration, credential: C) -> Self {
Self::builder(bucket, upload_token_lifetime, credential).build()
}
/// 创建存储空间上传凭证构建器
#[inline]
pub fn builder(
bucket: impl Into<BucketName>,
upload_token_lifetime: Duration,
credential: C,
) -> BucketUploadTokenProviderBuilder<C> {
BucketUploadTokenProviderBuilder {
inner: Self {
bucket: bucket.into(),
upload_token_lifetime,
credential,
on_policy_generated: None,
},
}
}
fn make_policy(&self) -> AnyResult<UploadPolicy> {
let mut builder =
UploadPolicyBuilder::new_policy_for_bucket(self.bucket.to_string(), self.upload_token_lifetime);
if let Some(on_policy_generated) = self.on_policy_generated.as_ref() {
on_policy_generated(&mut builder)?;
}
Ok(builder.build())
}
}
impl<C: Clone> Debug for BucketUploadTokenProvider<C> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("BucketUploadTokenProvider")
.field("bucket", &self.bucket)
.field("upload_token_lifetime", &self.upload_token_lifetime)
.finish()
}
}
impl<C: CredentialProvider + Clone> UploadTokenProvider for BucketUploadTokenProvider<C> {
#[inline]
fn access_key(&self, _opts: GetAccessKeyOptions) -> ParseResult<GotAccessKey> {
Ok(self
.credential
.get(Default::default())?
.into_credential()
.split()
.0
.into())
}
fn policy(&self, _opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy<'_>> {
Ok(self.make_policy()?.into())
}
fn to_token_string(&self, _opts: ToStringOptions) -> ToStringResult<Cow<'_, str>> {
Ok(Cow::Owned(
self.credential
.get(Default::default())?
.sign_with_data(self.make_policy()?.as_json().as_bytes()),
))
}
}
/// 存储空间上传凭证构建器
#[derive(Clone)]
pub struct BucketUploadTokenProviderBuilder<C: Clone> {
inner: BucketUploadTokenProvider<C>,
}
impl<C: Clone> BucketUploadTokenProviderBuilder<C> {
/// 设置上传凭证回调函数
#[inline]
#[must_use]
pub fn on_policy_generated(
mut self,
callback: impl Fn(&mut UploadPolicyBuilder) -> AnyResult<()> + Sync + Send + 'static,
) -> Self {
self.inner.on_policy_generated = Some(Arc::new(callback));
self
}
/// 构造存储空间上传凭证
#[inline]
pub fn build(self) -> BucketUploadTokenProvider<C> {
self.inner
}
}
impl<C: Clone> Debug for BucketUploadTokenProviderBuilder<C> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("BucketUploadTokenProviderBuilder")
.field("bucket", &self.inner.bucket)
.field("upload_token_lifetime", &self.inner.upload_token_lifetime)
.finish()
}
}
/// 基于对象的动态生成
///
/// 根据对象的快速生成上传凭证实例
#[derive(Clone)]
pub struct ObjectUploadTokenProvider<C: Clone> {
bucket: BucketName,
object: ObjectName,
upload_token_lifetime: Duration,
credential: C,
on_policy_generated: Option<OnPolicyGeneratedCallback>,
}
impl<C: Clone> ObjectUploadTokenProvider<C> {
/// 基于存储空间和对象名称和认证信息动态生成上传凭证实例
#[inline]
pub fn new(
bucket: impl Into<BucketName>,
object: impl Into<ObjectName>,
upload_token_lifetime: Duration,
credential: C,
) -> Self {
Self::builder(bucket, object, upload_token_lifetime, credential).build()
}
/// 创建对象上传凭证构建器
#[inline]
pub fn builder(
bucket: impl Into<BucketName>,
object: impl Into<ObjectName>,
upload_token_lifetime: Duration,
credential: C,
) -> ObjectUploadTokenProviderBuilder<C> {
ObjectUploadTokenProviderBuilder {
inner: Self {
bucket: bucket.into(),
object: object.into(),
upload_token_lifetime,
credential,
on_policy_generated: None,
},
}
}
fn make_policy(&self) -> AnyResult<UploadPolicy> {
let mut builder = UploadPolicyBuilder::new_policy_for_object(
self.bucket.to_string(),
self.object.to_string(),
self.upload_token_lifetime,
);
if let Some(on_policy_generated) = self.on_policy_generated.as_ref() {
on_policy_generated(&mut builder)?;
}
Ok(builder.build())
}
}
impl<C: Clone> Debug for ObjectUploadTokenProvider<C> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("ObjectUploadTokenProvider")
.field("bucket", &self.bucket)
.field("object", &self.object)
.field("upload_token_lifetime", &self.upload_token_lifetime)
.finish()
}
}
impl<C: CredentialProvider + Clone> UploadTokenProvider for ObjectUploadTokenProvider<C> {
fn access_key(&self, _opts: GetAccessKeyOptions) -> ParseResult<GotAccessKey> {
Ok(self
.credential
.get(Default::default())?
.into_credential()
.split()
.0
.into())
}
fn policy(&self, _opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy<'_>> {
Ok(self.make_policy()?.into())
}
fn to_token_string(&self, _opts: ToStringOptions) -> ToStringResult<Cow<'_, str>> {
Ok(Cow::Owned(
self.credential
.get(Default::default())?
.sign_with_data(self.make_policy()?.as_json().as_bytes()),
))
}
sourcepub fn from_json(json: impl AsRef<[u8]>) -> Result<UploadPolicy>
pub fn from_json(json: impl AsRef<[u8]>) -> Result<UploadPolicy>
解析 JSON 格式的上传凭证
Examples found in repository?
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
fn policy(&self, _opts: GetPolicyOptions) -> ParseResult<GotUploadPolicy<'_>> {
self.policy
.get_or_try_init(|| {
let encoded_policy = self
.upload_token
.splitn(3, ':')
.last()
.ok_or(ParseError::InvalidUploadTokenFormat)?;
let decoded_policy =
base64::decode(encoded_policy.as_bytes()).map_err(ParseError::Base64DecodeError)?;
UploadPolicy::from_json(decoded_policy).map_err(ParseError::JsonDecodeError)
})
.map(Cow::Borrowed)
.map(GotUploadPolicy::from)
}
sourcepub fn get(&self, key: impl JsonValueIndex) -> Option<&JsonValue>
pub fn get(&self, key: impl JsonValueIndex) -> Option<&JsonValue>
根据指定的上传策略字段获取相应的值
Examples found in repository?
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
pub fn bucket(&self) -> Option<&str> {
self.get(SCOPE_KEY)
.as_ref()
.and_then(|s| s.as_str())
.and_then(|s| s.split(':').next())
}
/// 对象名称约束或对象名称前缀约束
pub fn key(&self) -> Option<&str> {
self.get(SCOPE_KEY)
.as_ref()
.and_then(|v| v.as_str())
.and_then(|s| s.split_once(':').map(|x| x.1))
}
/// 是否是对象名称前缀约束
pub fn use_prefixal_object_key(&self) -> bool {
self.get(IS_PREFIXAL_SCOPE_KEY).and_then(|v| v.as_u64()).is_some()
}
/// 是否仅允许新增对象,不允许覆盖对象
pub fn is_insert_only(&self) -> bool {
self.get(INSERT_ONLY_KEY).and_then(|v| v.as_u64()).unwrap_or_default() > 0
}
/// 是否启用 MIME 类型自动检测
pub fn mime_detection_enabled(&self) -> bool {
self.get(DETECT_MIME_KEY).and_then(|v| v.as_u64()).unwrap_or_default() > 0
}
/// 上传凭证过期时间
pub fn token_deadline(&self) -> Option<SystemTime> {
self.get(DEADLINE_KEY)
.and_then(|v| v.as_u64())
.map(|t| SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(t)).unwrap())
}
/// Web 端文件上传成功后,浏览器执行 303 跳转的 URL
pub fn return_url(&self) -> Option<&str> {
self.get(RETURN_URL_KEY).and_then(|v| v.as_str())
}
/// 上传成功后,自定义七牛云最终返回给上传端的数据
pub fn return_body(&self) -> Option<&str> {
self.get(RETURN_BODY_KEY).and_then(|v| v.as_str())
}
/// 上传成功后,七牛云向业务服务器发送 POST 请求的 URL 列表
pub fn callback_urls(&self) -> Option<Split<char>> {
self.get(CALLBACK_URL_KEY)
.and_then(|v| v.as_str())
.map(|s| s.split(';'))
}
/// 上传成功后,七牛云向业务服务器发送回调请求时的 `Host`
pub fn callback_host(&self) -> Option<&str> {
self.get(CALLBACK_HOST_KEY).and_then(|v| v.as_str())
}
/// 上传成功后,七牛云向业务服务器发送回调请求时的内容
///
/// 支持[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)和[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)
pub fn callback_body(&self) -> Option<&str> {
self.get(CALLBACK_BODY_KEY).and_then(|v| v.as_str())
}
/// 上传成功后,七牛云向业务服务器发送回调请求时的 `Content-Type`
///
/// 默认为 `application/x-www-form-urlencoded`,也可设置为 `application/json`
pub fn callback_body_type(&self) -> Option<&str> {
self.get(CALLBACK_BODY_TYPE_KEY).and_then(|v| v.as_str())
}
/// 自定义对象名称
///
/// 支持[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)和[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)
pub fn save_key(&self) -> Option<&str> {
self.get(SAVE_KEY_KEY).and_then(|v| v.as_str())
}
/// 是否忽略客户端指定的对象名称,强制使用自定义对象名称进行文件命名
pub fn is_save_key_forced(&self) -> bool {
self.get(FORCE_SAVE_KEY_KEY).and_then(|v| v.as_bool()).unwrap_or(false)
}
/// 限定上传文件尺寸的范围
///
/// 返回的第一个元素为最小尺寸,第二个元素为最大尺寸,如果为 `None` 表示不限制,单位为字节
pub fn file_size_limitation(&self) -> (Option<u64>, Option<u64>) {
(
self.get(FSIZE_MIN_KEY).and_then(|v| v.as_u64()),
self.get(FSIZE_LIMIT_KEY).and_then(|v| v.as_u64()),
)
}
/// 限定用户上传的文件类型
///
/// 指定本字段值,七牛服务器会侦测文件内容以判断 MIME 类型,再用判断值跟指定值进行匹配,
/// 匹配成功则允许上传,匹配失败则返回 403 状态码
pub fn mime_types(&self) -> Option<Split<char>> {
self.get(MIME_LIMIT_KEY).and_then(|v| v.as_str()).map(|s| s.split(';'))
}
/// 文件类型
pub fn file_type(&self) -> Option<FileType> {
self.get(FILE_TYPE_KEY).and_then(|v| v.as_u64()).map(FileType::from)
}
/// 对象生命周期
///
/// 精确到天
pub fn object_lifetime(&self) -> Option<Duration> {
self.get(DELETE_AFTER_DAYS_KEY)
.and_then(|v| v.as_u64())
.map(|d| Duration::from_secs(d * 60 * 60 * 24))
}
sourcepub fn keys(&self) -> JsonMapKeys<'_>
pub fn keys(&self) -> JsonMapKeys<'_>
获取上传策略的字段迭代器
sourcepub fn values(&self) -> JsonMapValues<'_>
pub fn values(&self) -> JsonMapValues<'_>
获取上传策略的字段值的迭代器
sourcepub fn into_dynamic_upload_token_provider<T: CredentialProvider + Clone>(
self,
credential: T
) -> FromUploadPolicy<T>
pub fn into_dynamic_upload_token_provider<T: CredentialProvider + Clone>(
self,
credential: T
) -> FromUploadPolicy<T>
将上传策略转换为动态上传凭证提供者的实例
该方法与 UploadPolicy::into_static_upload_token_provider
的区别在于该方法接受 CredentialProvider
实例
Examples found in repository?
260 261 262 263 264 265 266 267 268 269 270 271 272 273
pub fn into_static_upload_token_provider(
self,
credential: Credential,
opts: ToStringOptions,
) -> StaticUploadTokenProvider {
let provider = self.into_dynamic_upload_token_provider(credential);
let token = provider.to_token_string(opts).unwrap();
let token: StaticUploadTokenProvider = token.parse().unwrap();
let (policy, credential) = provider.split();
let (access_key, _) = credential.split();
token.set_policy(policy);
token.set_access_key(access_key);
token
}
sourcepub fn into_static_upload_token_provider(
self,
credential: Credential,
opts: ToStringOptions
) -> StaticUploadTokenProvider
pub fn into_static_upload_token_provider(
self,
credential: Credential,
opts: ToStringOptions
) -> StaticUploadTokenProvider
将上传策略转换为静态上传凭证提供者的实例
该方法与 UploadPolicy::into_dynamic_upload_token_provider
的区别在于该方法只能接受 Credential
实例
Trait Implementations§
source§impl Clone for UploadPolicy
impl Clone for UploadPolicy
source§fn clone(&self) -> UploadPolicy
fn clone(&self) -> UploadPolicy
1.0.0 · source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source
. Read moresource§impl Debug for UploadPolicy
impl Debug for UploadPolicy
source§impl<'a> From<&'a UploadPolicy> for GotUploadPolicy<'a>
impl<'a> From<&'a UploadPolicy> for GotUploadPolicy<'a>
source§fn from(policy: &'a UploadPolicy) -> Self
fn from(policy: &'a UploadPolicy) -> Self
source§impl<'p> From<&'p UploadPolicy> for Cow<'p, UploadPolicy>
impl<'p> From<&'p UploadPolicy> for Cow<'p, UploadPolicy>
source§fn from(policy: &'p UploadPolicy) -> Self
fn from(policy: &'p UploadPolicy) -> Self
source§impl<'p> From<Cow<'p, UploadPolicy>> for UploadPolicy
impl<'p> From<Cow<'p, UploadPolicy>> for UploadPolicy
source§fn from(policy: Cow<'p, UploadPolicy>) -> Self
fn from(policy: Cow<'p, UploadPolicy>) -> Self
source§impl From<GotUploadPolicy<'_>> for UploadPolicy
impl From<GotUploadPolicy<'_>> for UploadPolicy
source§fn from(result: GotUploadPolicy<'_>) -> Self
fn from(result: GotUploadPolicy<'_>) -> Self
source§impl From<UploadPolicy> for Cow<'_, UploadPolicy>
impl From<UploadPolicy> for Cow<'_, UploadPolicy>
source§fn from(policy: UploadPolicy) -> Self
fn from(policy: UploadPolicy) -> Self
source§impl From<UploadPolicy> for GotUploadPolicy<'_>
impl From<UploadPolicy> for GotUploadPolicy<'_>
source§fn from(policy: UploadPolicy) -> Self
fn from(policy: UploadPolicy) -> Self
source§impl From<UploadPolicy> for UploadPolicyBuilder
impl From<UploadPolicy> for UploadPolicyBuilder
source§fn from(policy: UploadPolicy) -> Self
fn from(policy: UploadPolicy) -> Self
source§impl PartialEq<UploadPolicy> for UploadPolicy
impl PartialEq<UploadPolicy> for UploadPolicy
source§fn eq(&self, other: &UploadPolicy) -> bool
fn eq(&self, other: &UploadPolicy) -> bool
self
and other
values to be equal, and is used
by ==
.